diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 579d0a34..fb75cb60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: check_mode: "compile" no_compile_flag: false exclude_dirs: '["failing", "experiments"]' - compiler_ref: ${{ needs.find-latest-release.outputs.ref }} + compiler_ref: "master" # ${{ needs.find-latest-release.outputs.ref }} check-format: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index dbf48c1d..4d594143 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,9 @@ **/src-gen/ **/fed-gen/ C/include -Cpp/include -Cpp/share -Cpp/lib +Cpp/**/include +Cpp/**/share +Cpp/**/lib # Created by https://www.toptal.com/developers/gitignore/api/intellij,gradle,eclipse,maven,visualstudiocode # Edit at https://www.toptal.com/developers/gitignore?templates=intellij,gradle,eclipse,maven,visualstudiocode diff --git a/README.md b/README.md index 3ac79ed5..1cc03d58 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,25 @@ [![CI](https://github.com/lf-lang/examples-lingua-franca/actions/workflows/ci.yml/badge.svg)](https://github.com/lf-lang/examples-lingua-franca/actions/workflows/ci.yml) ## 🛝 Lingua Franca Playground -Get to know the language and tinker with some example Lingua Franca programs! +Get to know the [Lingua Franca coordination language](https://lf-lang.org) and browse [example programs](examples/README.md). +To view, edit, and run the programs, you can either run locally on your computer or run in the cloud using either GitHub Codespaces or Gitpod. -### :rocket: Cloud-based dev environment +## 💻 Running Locally +Quick start: + +1. Clone this repo (`git clone git@github.com:lf-lang/playground-lingua-franca.git`) +2. Open with [VS Code](https://code.visualstudio.com) (`code playground-lingua-franca`) +3. Install the [Lingua Franca extension](https://github.com/lf-lang/vscode-lingua-franca) (Ctrl+P and enter `ext install lf-lang.vscode-lingua-franca`). + +Once in VSCode, navigate to [./examples](./examples) and click on any of the `.lf` files to open them into your editor. To build and run, use Ctrl+Shift+P and select `Lingua Franca: Build and Run`. Note: You might need not install additional dependencies in order to successfully build some of the code you find in this repository. For more information, see the [setup-env.bash](./utils/scripts/setup-env.bash) script that we use to configure our Docker-based environments. + + +## :rocket: Running in the Cloud Spin up a fully configured dev environment in the cloud that start in seconds. Any dependencies required for building or running any of the examples are preinstalled. A web-based VS Code editor, preloaded with the Lingua Franca extension, is accessible through either **GitHub Codespaces** or **GitPod**. Simply click on either of the links below to get started. -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&repo=477928779&ref=main&skip_quickstart=true&devcontainer_path=.devcontainer%2Fnightly%2Fdevcontainer.json) (NOTE: This can be quite slow to start.) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/new#https://github.com/lf-lang/playground-lingua-franca/tree/main) @@ -32,9 +43,3 @@ bash ./utils/scripts/setup-lf.bash dev Please note that running these commands will remove the current lingua-franca directory. -## 💻 Local dev environment -1. Clone this repo (`git clone git@github.com:lf-lang/playground-lingua-franca.git`) -2. Open with VS Code (`code playground-lingua-franca`) -3. Install the [Lingua Franca extension](https://github.com/lf-lang/vscode-lingua-franca) (Ctrl+P and enter `ext install lf-lang.vscode-lingua-franca`). - -Once in VSCode, navigate to [./examples](./examples) and click on any of the `.lf` files to open them into your editor. To build and run, use Ctrl+Shift+P and select `Lingua Franca: Build and Run`. Note: You might need not install additional dependencies in order to successfully build some of the code you find in this repository. For more information, see the [setup-env.bash](./utils/scripts/setup-env.bash) script that we use to configure our Docker-based environments. diff --git a/examples/C/README.md b/examples/C/README.md index e9fd4742..50e9ed67 100644 --- a/examples/C/README.md +++ b/examples/C/README.md @@ -1,13 +1,20 @@ # C Examples -* [Patterns](src/patterns/README.md): Common communication patterns. + +(in alphabetical order) + +* [Browser UIs](src/browser-ui/README.md): How to create a browser-based UI for an LF program. +* [Car Brake](src/car-brake/README.md): Sketch of ADAS system illustrating the CAL theorem. * [Deadlines](src/deadlines/README.md): Uses of deadlines in Lingua Franca. -* [Car Brake](src/car-brake/README.md): Sketch of ADAS system illustrating CAL theorem. -* [Rosace](src/rosace/README.md): Aircraft controller illustrating periodic systems with multiple periods. -* [Simulation](src/simulation/README.md): Using Lingua Franca for simulation. +* [Distributed](src/distributed/README.md): Basic federated hello-world examples. +* [Furuta Pendulum](src/modal_models/FurutaPendulum/README.md): A controller and simulation illustrating a modal reactor. * [Keyboard](src/keyboard/README.md): Responding to keyboard input using ncurses. +* [Leader Election](src/leader-election/README.md): Federated fault-tolerant system with leader election. * [MQTT](src/mqtt/README.md): Interacting with MQTT-based services. -* [Furuta Pendulum](src/modal_models/FurutaPendulum/README.md): A controller and simulation illustrating a modal reactor. +* [Patterns](src/patterns/README.md): Common communication patterns. * [Rhythm](src/rhythm/README.md): Sound generation and terminal user interface demos. +* [Rosace](src/rosace/README.md): Aircraft controller illustrating periodic systems with multiple periods. * [SDV](src/sdv/README.md): Software defined vehicle sketch integrating user input, a web display, and sound. +* [Shared Memory](src/shared-memory/README.md): Using shared memory to exchange large data objects between federates. +* [Simulation](src/simulation/README.md): Using Lingua Franca for simulation. * [Train Door](src/train-door/README.md): Train door controller from a verification paper. -* [Distributed](src/distributed/README.md): Basic federated hello-world examples. \ No newline at end of file +* [Watchdog](src/watchdog/README.md): Federated illustration of watchdogs. diff --git a/examples/C/src/ReflexGame/ReflexGame.lf b/examples/C/src/ReflexGame/ReflexGame.lf index 78ef6c3d..e776b5ff 100644 --- a/examples/C/src/ReflexGame/ReflexGame.lf +++ b/examples/C/src/ReflexGame/ReflexGame.lf @@ -13,10 +13,6 @@ target C { keepalive: true } -preamble {= - #include "include/core/platform.h" -=} - /** * Produce a counting sequence at random times with a minimum and maximum time between outputs * specified as parameters. diff --git a/examples/C/src/ReflexGame/ReflexGameTest.lf b/examples/C/src/ReflexGame/ReflexGameTest.lf index 8621be15..f46ef52b 100644 --- a/examples/C/src/ReflexGame/ReflexGameTest.lf +++ b/examples/C/src/ReflexGame/ReflexGameTest.lf @@ -12,10 +12,6 @@ target C { timeout: 5 sec } -preamble {= - #include "include/core/platform.h" -=} - main reactor { preamble {= // Specify a thread that sends all keyboard characters diff --git a/examples/C/src/SleepingBarber.lf b/examples/C/src/SleepingBarber.lf index b1d211b4..ecc6c741 100644 --- a/examples/C/src/SleepingBarber.lf +++ b/examples/C/src/SleepingBarber.lf @@ -26,7 +26,7 @@ */ target C { fast: true, - threading: false, + single-threaded: true, cmake-include: "/lib/c/reactor-c/util/deque.cmake", files: ["/lib/c/reactor-c/util/deque.h", "/lib/c/reactor-c/util/deque.c"] } diff --git a/examples/C/src/browser-ui/README.md b/examples/C/src/browser-ui/README.md new file mode 100644 index 00000000..f41ae0b3 --- /dev/null +++ b/examples/C/src/browser-ui/README.md @@ -0,0 +1,23 @@ +# Browser UI + +These examples show how to create user interfaces for a Lingua Franca program. +The UI runs in the browser and connects to the program via either HTTP or via a web socket. + + + + + + + + + + + + + + +
BrowserUI + BrowserUI.lf: This version starts a web server that serves a specified web page and enables implementing an HTTP-based API to control your LF program. When the program is running, you can point your browser to http://localhost:8080 to get a page. Adding a path to the URL, as in for example, http://localhost:8080/count, will cause the ServerUI reactor to produce an output that your LF program can react to and send a (text) response.
WebSocket + WebSocket.lf: This example uses the much more versatile WebSocketServer reactor. When the program is running, you can open an HTML page that includes JavaScript that connects to the server. Messages can be sent in both directions over the web socket, from the LF program to the browser and vice versa.
WebSocketString + WebSocketString.lf: This version uses the simpler WebSocketServerString reactor, which is simpler in that it restricts the messages transported over the web socket to be of string types and it allows only one client to connect.
Uptime + Uptime.lf: This version combines ServerUI with WebSocketServer to serve a web page and then feed it data continuously through a web socket. The application displays the total time that application has been running and updates this time once per second.
\ No newline at end of file diff --git a/examples/C/src/browser-ui/Uptime.lf b/examples/C/src/browser-ui/Uptime.lf new file mode 100644 index 00000000..da0cc55f --- /dev/null +++ b/examples/C/src/browser-ui/Uptime.lf @@ -0,0 +1,53 @@ +/** + * This example combines `ServerUI` with `WebSocketServer`. The former starts a web server that + * listens for HTTP requests on port 8080 and serves the web page defined in `uptime.html`. That web + * page includes JavaScript that connects to a web socket on port 8080 that is provided by the + * `WebSocketServer` reactor. The resulting web page simply reports the total time that this program + * has been running. That time is updated on the web page once per second. + * + * This uses the libwebsockets (see API documentation and installation + * instructions). To install on MacOS, we recommending using brew: + *
 brew install libwebsockets
+ * 
This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be + * linked to using the {@code -lwebsockets} compile option or the {@code WebSocketCmake.txt} Cmake + * include file. + * + * @author Edward A. Lee + */ +target C { + build-type: debug, + keepalive: true +} + +import WebSocketServer from "../lib/WebSocketServer.lf" + +main reactor { + timer seconds(0, 1 s) + + w = new WebSocketServer( + hostport=8080, + initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "Uptime.html" =}) + + reaction(startup) {= + lf_print("Point your browser to http://localhost:8080"); + =} + + reaction(seconds) -> w.send {= + instant_t uptime = lf_time_logical_elapsed(); + // Truncate to the nearest second. + uptime = (uptime / SEC(1)) * SEC(1); + char* message = (char*)malloc(LF_TIME_BUFFER_LENGTH * sizeof(char)); + size_t length = lf_readable_time(message, uptime) + 1; // +1 to add a null character. + message[length] = '\0'; + + // Broadcast to all connected sockets. This is accomplished by providing a NULL wsi. + web_socket_message_t* to_send = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); + to_send->wsi = NULL; + to_send->length = length; + to_send->message = message; + to_send->binary = false; + lf_set(w.send, to_send); + =} +} diff --git a/examples/C/src/browser-ui/WebSocket.html b/examples/C/src/browser-ui/WebSocket.html index 80e0299f..8d9a1f78 100644 --- a/examples/C/src/browser-ui/WebSocket.html +++ b/examples/C/src/browser-ui/WebSocket.html @@ -9,11 +9,12 @@ // Get references to elements on the page. var form = document.getElementById('message-form'); var messageField = document.getElementById('message'); - var messagesList = document.getElementById('messages'); + var incoming = document.getElementById('messages'); + var outgoing = document.getElementById('outgoing'); var socketStatus = document.getElementById('status'); var closeBtn = document.getElementById('close'); - const socket = new WebSocket('ws://localhost:8000', 'ws'); + const socket = new WebSocket('ws://localhost:8080', 'ws'); socket.addEventListener('open', (event) => { console.log('WebSocket connection established'); @@ -38,9 +39,9 @@ // Send the message through the WebSocket. if (socket.readyState == WebSocket.OPEN) { socket.send(message); - messagesList.innerHTML = '
  • Sent:' + message + '
  • '; + outgoing.innerHTML = message; } else { - messagesList.innerHTML = '
  • Socket is not open!
  • '; + outgoing.innerHTML = 'Socket is not open!'; } // Clear out the message field. @@ -52,8 +53,7 @@ // Handle messages sent by the server. socket.onmessage = function(event) { var message = event.data; - messagesList.innerHTML = '
  • Received:' + - message + '
  • '; + incoming.innerHTML = message; }; // Show a disconnected message when the WebSocket is closed. @@ -76,8 +76,10 @@

    WebSockets Demo

    Connecting...
    - - +
    diff --git a/examples/C/src/browser-ui/WebSocket.lf b/examples/C/src/browser-ui/WebSocket.lf index fcb2d594..b87c5668 100644 --- a/examples/C/src/browser-ui/WebSocket.lf +++ b/examples/C/src/browser-ui/WebSocket.lf @@ -8,10 +8,18 @@ * the WebSocket.html web page more than twice, only the first two attempts will succeed in * connecting. By default, WebSocketServer imposes no such limit. * - * This example also shows one way to keep track of multiple connections. It uses a hashset - * containing pointers to the wsi (web socket interface) fields, which are unique to each - * connection. In this example, if you connect two clients, one will receive even numbered counts, - * and the other will receive odd numbered counts. + * In this example, if you connect two clients, one will receive even numbered counts, and the other + * will receive odd numbered counts. This is because the count state variable is shared across all + * clients. + * + * This uses the libwebsockets (see API documentation and installation + * instructions). To install on MacOS, we recommending using brew: + *
     brew install libwebsockets
    + * 
    This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be + * linked to using the {@code -lwebsockets} compile option or the {@code WebSocketCmake.txt} Cmake + * include file. * * @author Edward A. Lee */ @@ -21,68 +29,47 @@ target C { import WebSocketServer from "../lib/WebSocketServer.lf" -preamble {= - #include "hashset/hashset.h" // For storing web socket instances that are connected. -=} - main reactor { state count: int = 0 logical action send_action: web_socket_instance_t* - state connected_instances: hashset_t = {= NULL =} - - s = new WebSocketServer(max_clients=2) // Limit number of clients to 2. - - reaction(startup) -> s.send {= - lf_set_destructor(s.send, web_socket_message_destructor); - lf_set_copy_constructor(s.send, web_socket_message_copy_constructor); + s = new WebSocketServer( + hostport=8080, + max_clients=2, // Limit number of clients to 2. + initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "WebSocket.html" =}) + reaction(startup) {= lf_print("======== Starting server. Open WebSocket.html in your favorite browser."); - self->connected_instances = hashset_create(2); // Default capacity for four instances. =} reaction(send_action) -> s.send, send_action {= - // If the web socket is no longer connected, the instance will not be in the hashset. - if (hashset_is_member(self->connected_instances, send_action->value->wsi)) { - char* message; - asprintf(&message, "Count is: %d", self->count++); + char* message; + asprintf(&message, "Count is: %d", self->count++); - web_socket_message_t* container = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); - container->message = message; - container->length = strlen(message) + 1; - container->wsi = send_action->value->wsi; + web_socket_message_t* container = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); + container->message = message; + container->length = strlen(message) + 1; + container->wsi = send_action->value->wsi; + container->binary = false; // Sending text. - lf_set(s.send, container); - // Schedule the next send. - lf_schedule_token(send_action, SEC(1), send_action->token); - } + lf_set(s.send, container); + // Schedule the next send. + lf_schedule_token(send_action, SEC(1), send_action->token); =} reaction(s.connected) -> send_action {= if (s.connected->value.connected) { lf_print("======== Connected a new client"); - } else { - lf_print("======== Disconnected client"); - } - if (s.connected->value.connected) { - // New connection. - hashset_add(self->connected_instances, s.connected->value.wsi); // Start sending. lf_schedule_copy(send_action, 0, &s.connected->value, 1); } else { - // Disconnecting. Remove from hashset to prevent further scheduling. - hashset_remove(self->connected_instances, s.connected->value.wsi); + lf_print("======== Disconnected client"); } =} reaction(s.received) {= - lf_print("======== Application received: %s", s.received->value->message); - =} - - reaction(shutdown) {= - if (self->connected_instances != NULL) { - hashset_destroy(self->connected_instances); - } + // NOTE: The WebSocketServer ensures that the received message is null terminated. + lf_print("======== Application received: %s", (char*)s.received->value->message); =} } diff --git a/examples/C/src/browser-ui/WebSocketString.lf b/examples/C/src/browser-ui/WebSocketString.lf new file mode 100644 index 00000000..5e6c38b4 --- /dev/null +++ b/examples/C/src/browser-ui/WebSocketString.lf @@ -0,0 +1,64 @@ +/** + * Demo of a use of WebSocketServerString enabling a user interface realized in the browser. Compile + * and run this program, then open WebSocket.html in your favorite browser. This example program + * sends to the web page a counting sequence. It also accepts text messages from the web page and + * prints them on standard output. + * + * This example limits the number of connections to just one. Its purpose is to show how to use the + * simpler WebSocketServerString when you need just one connection and the data exchanged is always + * a string. + * + * @author Edward A. Lee + */ +target C { + keepalive: true +} + +import WebSocketServerString from "../lib/WebSocketServerString.lf" + +main reactor { + state count: int = 0 + state connected: bool = false + + logical action send_action + + s = new WebSocketServerString() // Limit number of clients to 2. + + reaction(startup) {= + lf_print("======== Starting server. Open WebSocket.html in your favorite browser."); + =} + + reaction(send_action) -> s.in_dynamic, send_action {= + // Send using the `in_dynamic` port because the string sent is dynamically allocated. + // If the web socket is no longer connected, do nothing. + if (self->connected) { + // Create the message. + char* message; + asprintf(&message, "Count is: %d", self->count++); + + // Send the message. + lf_set(s.in_dynamic, message); + + // Schedule the next send. + lf_schedule(send_action, SEC(1)); + } + =} + + reaction(s.connected) -> send_action {= + if (s.connected->value) { + lf_print("======== Client has connected"); + } else { + lf_print("======== Client has disconnected"); + } + self->connected = s.connected->value; + if (s.connected->value) { + // Start sending. + lf_schedule(send_action, 0); + } + =} + + reaction(s.received) {= + // NOTE: The WebSocketServerString ensures that the received message is null terminated. + lf_print("======== Application received: %s", (char*)s.received->value); + =} +} diff --git a/examples/C/src/browser-ui/img/BrowserUI.png b/examples/C/src/browser-ui/img/BrowserUI.png new file mode 100644 index 00000000..dd0d006c Binary files /dev/null and b/examples/C/src/browser-ui/img/BrowserUI.png differ diff --git a/examples/C/src/browser-ui/img/Uptime.png b/examples/C/src/browser-ui/img/Uptime.png new file mode 100644 index 00000000..073765fd Binary files /dev/null and b/examples/C/src/browser-ui/img/Uptime.png differ diff --git a/examples/C/src/browser-ui/img/WebSocket.png b/examples/C/src/browser-ui/img/WebSocket.png new file mode 100644 index 00000000..67d23021 Binary files /dev/null and b/examples/C/src/browser-ui/img/WebSocket.png differ diff --git a/examples/C/src/browser-ui/img/WebSocketString.png b/examples/C/src/browser-ui/img/WebSocketString.png new file mode 100644 index 00000000..70f699b8 Binary files /dev/null and b/examples/C/src/browser-ui/img/WebSocketString.png differ diff --git a/examples/C/src/browser-ui/uptime.html b/examples/C/src/browser-ui/uptime.html new file mode 100644 index 00000000..5d3eae40 --- /dev/null +++ b/examples/C/src/browser-ui/uptime.html @@ -0,0 +1,42 @@ + + + Uptime Lingua Franca + +

    The Uptime Lingua Franca program has been running for:

    +

    No data yet

    + This page connects to a Lingua Franca program that feeds it time data through a web socket. +

    Not connected

    + + + diff --git a/examples/C/src/distributed/CAL.lf b/examples/C/src/distributed/CAL.lf new file mode 100644 index 00000000..b9d14712 --- /dev/null +++ b/examples/C/src/distributed/CAL.lf @@ -0,0 +1,85 @@ +/** + * This program illustrates how relaxing consistency improves availability, as predicted by the CAL + * Theorem reported in the following paper: + * + * Edward A. Lee, Ravi Akella, Soroush Bateni, Shaokai Lin, Marten Lohstroh, Christian Menard. + * "Consistency vs. Availability in Distributed Cyber-Physical Systems". ACM Transactions on + * Embedded Computing Systems (TECS), September 2023. https://dl.acm.org/doi/10.1145/3609119 + * + * This program has two `Sense` sources of events, `s1` and `s2` with identical periods of 1s. The + * `s1` output is processed by the `Process` reactor, which takes at least 35ms to process the data + * (emulated by sleeping). As a consequence of this processing latency, were it not for the 200ms + * `after` delay, the 30ms deadline of the `Actuate` reactor would be violated every time. The + * deadline is on the second reaction, which cannot be invoked before the first reaction when both + * reactions are enabled. + * + * The deadline is an availability requirement, the `after` delay is a tolerance for inconsistency, + * and the 35ms processing time is a latency (The C A L in CAL). As long as the actual latency is + * not greater than the 200ms tolerance for inconsistency plus the 30ms tolerance for + * unavailability, then availability requirement will be met. + * + * Removing or reducing the after delay strengthens consistency but causes deadline violations. + * + * This program uses centralized coordination, so if the processing latency plus communication + * latency exceeds the 200ms tolerance for inconsistency and the 30ms tolerance for unavailability + * (the deadline), then the coordinator will preserve consistency at the expense of availability. + * I.e., the deadline will be violated. + * + * @author Edward A. Lee + */ +target C { + timeout: 5 s +} + +preamble {= + #include "platform.h" +=} + +// Produce a counting sequence starting with 1. +reactor Sense(period: time = 1 s) { + state count: int = 1 + output out: int + timer t(0, period) + + reaction(t) -> out {= + lf_set(out, self->count++); + =} +} + +// Pass the input unchanged to the output, but take a long time to do it. +reactor Process { + input in: int + output out: int + + reaction(in) -> out {= + lf_sleep(MSEC(35)); + lf_set(out, in->value); + =} +} + +// Report the inputs. +reactor Actuate { + input in1: int + input in2: int + + reaction(in1) {= + lf_print(PRINTF_TIME ": Received on in1: %d", lf_time_logical_elapsed(), in1->value); + =} + + reaction(in2) {= + lf_print(PRINTF_TIME ": Received on in2: %d", lf_time_logical_elapsed(), in2->value); + =} deadline(30 ms) {= + lf_print(PRINTF_TIME ": Received on in2: %d", lf_time_logical_elapsed(), in2->value); + lf_print(" *** PANIC! Deadline violated!"); + =} +} + +federated reactor { + s1 = new Sense() + s2 = new Sense() + c1 = new Process() + a = new Actuate() + s1.out -> c1.in + c1.out -> a.in1 after 200 ms + s2.out -> a.in2 +} diff --git a/examples/C/src/distributed/CALDecentralized.lf b/examples/C/src/distributed/CALDecentralized.lf new file mode 100644 index 00000000..434c0d3c --- /dev/null +++ b/examples/C/src/distributed/CALDecentralized.lf @@ -0,0 +1,67 @@ +/** + * This program illustrates how relaxing consistency improves availability, as predicted by the CAL + * Theorem reported in the following paper: + * + * Edward A. Lee, Ravi Akella, Soroush Bateni, Shaokai Lin, Marten Lohstroh, Christian Menard. + * "Consistency vs. Availability in Distributed Cyber-Physical Systems". ACM Transactions on + * Embedded Computing Systems (TECS), September 2023. https://dl.acm.org/doi/10.1145/3609119 + * + * This program is just like CAL.lf except that it uses decentralized coordination. + * + * Removing or reducing the after delay strengthens consistency but causes deadline violations. + * + * This program uses decentralized coordination, so if the processing latency plus communication + * latency exceeds the 200ms tolerance for inconsistency plus the 30ms tolerance for unavailability + * (the deadline), then the system can only preserve availability at the expense of consistency. + * However, in order to do that, you need to adjust the STP offset, which corresponds to the + * processing offset in the CAL theorem. If you remove the 200ms `after` delay, you will need to + * increase the STP offset in the `Actuate` reactor to ensure that it does not prematurely assume + * the inputs are absent. Any number significantly larger than the 35ms processing latency should be + * sufficient to prevent STP violations, but such a number will result in a deadline violation. + * Setting a lower number prevents the deadline violation, but at the cost of getting consistency + * violations. It is also possible to get _both_ deadline and consistency violations. + * + * @author Edward A. Lee + */ +target C { + coordination: decentralized, + timeout: 5 s +} + +import Sense, Process from "CAL.lf" + +// Override the base class to provide an STP offset. +reactor ProcessFed(STP_offset: time = 10 ms) extends Process { +} + +// Report inputs received. +// STP_offset parameter is needed to ensure receiving the last input at time 5s. +// This has to be less than the deadline or the deadline will always be violated. +reactor Actuate(STP_offset: time = 20 ms) { + input in1: int + input in2: int + + reaction(in1) {= + lf_print(PRINTF_TIME ": Received on in1: %d", lf_time_logical_elapsed(), in1->value); + =} STP(0) {= + // No _additional_ STP offset (STAA) is needed here. + lf_print(PRINTF_TIME ": *** Consistency violation! Received on in1: %d", lf_time_logical_elapsed(), in1->value); + =} + + reaction(in2) {= + lf_print(PRINTF_TIME ": Received on in2: %d", lf_time_logical_elapsed(), in2->value); + =} deadline(30 ms) {= + lf_print(PRINTF_TIME ": Received on in2: %d", lf_time_logical_elapsed(), in2->value); + lf_print(" *** PANIC! Deadline violated!"); + =} +} + +federated reactor { + s1 = new Sense() + s2 = new Sense() + c1 = new ProcessFed() + a = new Actuate() + s1.out -> c1.in + c1.out -> a.in1 after 200 ms + s2.out -> a.in2 +} diff --git a/examples/C/src/distributed/HelloWorld.lf b/examples/C/src/distributed/HelloWorld.lf index a409748d..3e42f723 100644 --- a/examples/C/src/distributed/HelloWorld.lf +++ b/examples/C/src/distributed/HelloWorld.lf @@ -38,10 +38,14 @@ reactor MessageGenerator(prefix: string = "") { // With NULL, 0 arguments, snprintf tells us how many bytes are needed. // Add one for the null terminator. int length = snprintf(NULL, 0, "%s %d", self->prefix, self->count) + 1; + // Dynamically allocate memory for the output. - SET_NEW_ARRAY(message, length); + char* buffer = malloc(length); + // Populate the output string and increment the count. - snprintf(message->value, length, "%s %d", self->prefix, self->count++); + snprintf(buffer, length, "%s %d", self->prefix, self->count++); + + lf_set_array(message, buffer, length); tag_t tag = lf_tag(); lf_print("At (elapsed) logical tag (%lld, %u), source sends message: %s", diff --git a/examples/C/src/distributed/README.md b/examples/C/src/distributed/README.md index 231e385a..102059fd 100644 --- a/examples/C/src/distributed/README.md +++ b/examples/C/src/distributed/README.md @@ -1,6 +1,6 @@ # Distributed Lingua Franca -When intead of a **main reactor** you defined a **federated reactor**, a Lingua Franca program gets divided into multiple programs that execute as separate processes and can execute on different machines. The coordination and communication is automatically generated to preserve LF semantics so that the programs will logically the same as a non-federated program. See [Distributed Execution documentation](https://www.lf-lang.org/docs/handbook/distributed-execution?target=c) for more details. +When intead of a **main reactor** you defined a **federated reactor**, a Lingua Franca program gets divided into multiple programs that execute as separate processes and can execute on different machines. The coordination and communication is automatically generated to preserve LF semantics so that the programs will logically behave the same as a non-federated program. See [Distributed Execution documentation](https://www.lf-lang.org/docs/handbook/distributed-execution?target=c) for more details. This directory includes a series of very simple "Hello World" examples. @@ -15,23 +15,32 @@ To run these programs, you are required to first [install the RTI](https://www.l HelloWorld HelloWorld.lf : A MessageGenerator produces a string and sends it over a network connection to a PrintMessage reactor that prints the message. + HelloWorldAfter HelloWorldAfter.lf : A variant with a logical time delay on the connection. - + HelloWorldDecentralized HelloWorldDecentralized.lf : A variant that uses decentralized coordination, which relies and clock synchronization. This version uses an **after** delay. - + HelloWorldDecentralizedSTP HelloWorldDecentralizedSTP.lf : A decentralized variant that uses a safe-to-process (STP) offset instead of an **after** delay. - + HelloWorldPhysical HelloWorldPhysical.lf : A variant with physical connection. - + HelloWorldPhysicalAfter HelloWorldPhysicalAfter.lf : A variant with a physical connection and an **after** delay. + + CAL + CAL.lf : An illustration of the fundamental tradeoff between consistency, availability, and latency, using centralized coordination. + + + CALDecentralized + CALDecentralized.lf : An illustration of the fundamental tradeoff between consistency, availability, and latency, using decentralized coordination. + diff --git a/examples/C/src/distributed/img/CAL.png b/examples/C/src/distributed/img/CAL.png new file mode 100644 index 00000000..72cc21ec Binary files /dev/null and b/examples/C/src/distributed/img/CAL.png differ diff --git a/examples/C/src/distributed/img/CALDecentralized.png b/examples/C/src/distributed/img/CALDecentralized.png new file mode 100644 index 00000000..776d7912 Binary files /dev/null and b/examples/C/src/distributed/img/CALDecentralized.png differ diff --git a/examples/C/src/leader-election/HeartbeatBully.lf b/examples/C/src/leader-election/HeartbeatBully.lf new file mode 100644 index 00000000..4e692d29 --- /dev/null +++ b/examples/C/src/leader-election/HeartbeatBully.lf @@ -0,0 +1,186 @@ +/** + * This program models a redundant fault tolerant system where a primary node, if and when it fails, + * is replaced by one of several backup nodes. The protocol is described in this paper: + * + * Bjarne Johansson; Mats Rågberger; Alessandro V. Papadopoulos; Thomas Nolte, "Heartbeat Bully: + * Failure Detection and Redundancy Role Selection for Network-Centric Controller," Proc. of the + * 46th Annual Conference of the IEEE Industrial Electronics Society (IECON), 8-21 October 2020. + * https://doi.org/10.1109/IECON43393.2020.9254494 + * + * The program has a bank of redundant nodes where exactly one is the primary node and the rest are + * backups. The primary node is always the one with the highest bank index that has not failed. The + * primary sends a heartbeat message once per second (by default). When the primary fails, a leader + * election protocol selects a new primary which then starts sending heartbeat messages. The program + * is set so that each primary fails after sending three heartbeat messages. When all nodes have + * failed, then the program exits. + * + * This example is designed to be run as a federated program. + * + * @author Edward A. Lee + * @author Marjan Sirjani + */ +target C { + timeout: 30 s +} + +preamble {= + #include "platform.h" // Defines PRINTF_TIME + enum message_type { + heartbeat, + reveal, + sorry + }; + typedef struct message_t { + enum message_type type; + int id; + } message_t; +=} + +reactor Node( + bank_index: int = 0, + num_nodes: int = 3, + heartbeat_period: time = 1 s, + max_missed_heartbeats: int = 2, + primary_fails_after_heartbeats: int = 3) { + input[num_nodes] in: message_t + output[num_nodes] out: message_t + + state heartbeats_missed: int = 0 + state primary_heartbeats_counter: int = 0 + + initial mode Idle { + reaction(startup) -> reset(Backup), reset(Primary) {= + lf_print(PRINTF_TIME ": Starting node %d", lf_time_logical_elapsed(), self->bank_index); + if (self->bank_index == self->num_nodes - 1) { + lf_set_mode(Primary); + } else { + lf_set_mode(Backup); + } + =} + } + + mode Backup { + timer t(heartbeat_period, heartbeat_period) + reaction(in) -> out, reset(Prospect) {= + int primary_id = -1; + for (int i = 0; i < in_width; i++) { + if (in[i]->is_present && in[i]->value.id != self->bank_index) { + if (in[i]->value.type == heartbeat) { + if (primary_id >= 0) { + lf_print_error("Multiple primaries detected!!"); + } + primary_id = in[i]->value.id; + lf_print(PRINTF_TIME ": Node %d received heartbeat from node %d.", lf_time_logical_elapsed(), self->bank_index, primary_id); + self->heartbeats_missed = 0; + } else if (in[i]->value.type == reveal && in[i]->value.id < self->bank_index) { + // NOTE: This will not occur if the LF semantics are followed because + // all nodes will (logically) simultaneously detect heartbeat failure and + // transition to the Prospect mode. But we include this anyway in case + // a federated version experiences a fault. + + // Send a sorry message. + message_t message; + message.type = sorry; + message.id = self->bank_index; + lf_set(out[in[i]->value.id], message); + lf_print(PRINTF_TIME ": Node %d sends sorry to node %d", lf_time_logical_elapsed(), self->bank_index, in[i]->value.id); + // Go to Prospect mode to send reveal to any higher-priority nodes. + lf_set_mode(Prospect); + } + } + } + =} + + reaction(t) -> reset(Prospect) {= + if (self->heartbeats_missed > self->max_missed_heartbeats) { + lf_set_mode(Prospect); + } + // Increment the counter so if it's not reset to 0 by the next time, + // we detect the missed heartbeat. + self->heartbeats_missed++; + =} + } + + mode Primary { + timer heartbeat(0, heartbeat_period) + reaction(heartbeat) -> out, reset(Failed) {= + if (self->primary_heartbeats_counter++ >= self->primary_fails_after_heartbeats) { + // Stop sending heartbeats. + lf_print(PRINTF_TIME ": **** Primary node %d fails.", lf_time_logical_elapsed(), self->bank_index); + lf_set_mode(Failed); + } else { + lf_print(PRINTF_TIME ": Primary node %d sends heartbeat.", lf_time_logical_elapsed(), self->bank_index); + for (int i = 0; i < out_width; i++) { + if (i != self->bank_index) { + message_t message; + message.type = heartbeat; + message.id = self->bank_index; + lf_set(out[i], message); + } + } + } + =} + } + + mode Failed { + } + + mode Prospect { + logical action wait_for_sorry + reaction(reset) -> out, wait_for_sorry {= + lf_print(PRINTF_TIME ": ***** Node %d entered Prospect mode.", lf_time_logical_elapsed(), self->bank_index); + // Send a reveal message with my ID in a bid to become primary. + // NOTE: It is not necessary to send to nodes that have a lower + // priority than this node, but the connection is broadcast, so + // we send to all. + message_t message; + message.type = reveal; + message.id = self->bank_index; + for (int i = self->bank_index + 1; i < self->num_nodes; i++) { + lf_print(PRINTF_TIME ": Node %d sends reveal to node %d", lf_time_logical_elapsed(), self->bank_index, i); + lf_set(out[i], message); + } + // The reveal message is delayed by heartbeat_period, and if + // there is a sorry response, it too will be delayed by heartbeat_period, + // so the total logical delay is twice heartbeat_period. + lf_schedule(wait_for_sorry, 2 * self->heartbeat_period); + =} + + reaction(in) -> out {= + for (int i = 0; i < in_width; i++) { + if (in[i]->value.type == reveal && in[i]->value.id < self->bank_index) { + // Send a sorry message. + message_t message; + message.type = sorry; + message.id = self->bank_index; + lf_set(out[in[i]->value.id], message); + lf_print(PRINTF_TIME ": Node %d sends sorry to node %d", lf_time_logical_elapsed(), self->bank_index, in[i]->value.id); + } + } + =} + + reaction(wait_for_sorry) in -> reset(Backup), reset(Primary) {= + // Check for sorry messages. + // Sorry messages are guaranteed to be logically simultaneous + // with the wait_for_sorry event, so we just need to check for + // presence of sorry inputs. + int i; + for (i = 0; i < in_width; i++) { + if (in[i]->is_present && in[i]->value.type == sorry) { + // A sorry message arrived. Go to Backup mode. + lf_set_mode(Backup); + break; + } + } + if (i == in_width) { + // No sorry message arrived. Go to Primary mode. + lf_set_mode(Primary); + } + =} + } +} + +federated reactor(num_nodes: int = 4, heartbeat_period: time = 1 s) { + nodes = new[num_nodes] Node(num_nodes=num_nodes, heartbeat_period=heartbeat_period) + nodes.out -> interleaved(nodes.in) after heartbeat_period +} diff --git a/examples/C/src/leader-election/NRP_FD.lf b/examples/C/src/leader-election/NRP_FD.lf new file mode 100644 index 00000000..2d34a76b --- /dev/null +++ b/examples/C/src/leader-election/NRP_FD.lf @@ -0,0 +1,521 @@ +/** + * This program implements a redundant fault-tolerant system where a primary node, if and when it + * fails, is replaced by a backup node. The protocol is described in this paper: + * + * Bjarne Johansson; Mats Rågberger; Alessandro V. Papadopoulos; Thomas Nolte, "Consistency Before + * Availability: Network Reference Point based Failure Detection for Controller Redundancy," + * Emerging Technologies and Factory Automation (ETFA), 12-15 September 2023, DOI: + * 10.1109/ETFA54631.2023.10275664 + * + * The key idea in this protocol is that when a backup fails to detect the heartbeats of a primary + * node, it becomes primary only if it has access to Network Reference Point (NRP), which is a point + * in the network. This way, if the network becomes partitioned, only a backup that is on the side + * of the partition that still has access to the NRP can become a primary. If a primary loses access + * to the NRP, then it relinquishes its primary role because it is now on the wrong side of a + * network partition. A backup on the right side of the partition will take over. + * + * This implementation omits some details in the paper. See NOTEs in the comments. + * + * This version has switch1 failing at 3s, node1 failing at 10s, and node2 failing at 15s. + * + * @author Edward A. Lee + * @author Marjan Sirjani + */ +target C { + tracing: true, + timeout: 20 s +} + +preamble {= + #ifndef NRF_FD + #define NRF_FD + #include "platform.h" // Defines PRINTF_TIME + + // Paper calls for manual intervention to set initial primary ID and NRP network. + // Here, we just hardwire this choice using #define. + #define INITIAL_PRIMARY_ID 1 + #define INITIAL_NRP_NETWORK 0 + + enum message_type { + heartbeat, + ping_NRP, + ping_NRP_response, + request_new_NRP, + new_NRP + }; + typedef struct message_t { + enum message_type type; + int source; + int destination; + int payload; + } message_t; + #endif // NRF_FD +=} + +reactor Node( + id: int = 0, + heartbeat_period: time = 1 s, + routine_ping_offset: time = 1 ms, // Time after heartbeat to ping NRP. + max_missed_heartbeats: int = 2, + fails_at_time: time = 0, // For testing. 0 for no failure. + ping_timeout: time = 500 ms, // Time until ping is deemed to have failed. + // Time until new NRP request is deemed to have failed. + nrp_timeout: time = 500 ms) { + // There are two network interfaces: + @side("east") + input[2] in: message_t + output[2] out: message_t + + timer node_fails(fails_at_time) + + state heartbeats_missed: int[2] = {0} + state primary: int = 0 // The known primary node. + state ping_pending: bool = false // Ping has been issued and not responded to. + state ping_timeout_pending: bool = false // Ping timeout timer hasn't expired. + state become_primary_on_ping_response: bool = false + state NRP_network: int = {= INITIAL_NRP_NETWORK =} + state NRP_switch_id: int = 0 // 0 means not known. + + logical action ping_timed_out(ping_timeout) + logical action new_NRP_request_timed_out(nrp_timeout) + + initial mode Waiting { + reaction(startup) -> out {= + // If I am the initial primary, broadcast a ping on network 1. + // The first switch to get this will respond. + if (self->id == INITIAL_PRIMARY_ID) { + message_t ping_message = {ping_NRP, self->id, 0, 0}; + lf_set(out[INITIAL_NRP_NETWORK], ping_message); + // Instead of scheduling ping_timed_out, we just continue waiting until a ping response arrives. + } + =} + + reaction(in) -> out, reset(Backup), reset(Primary) {= + // Iterate over input channels. + for (int c = 0; c < in_width; c++) { + if (in[c]->is_present) { + // In this mode, primary is waiting for a ping response and backup for a new NRP. + if (self->id == INITIAL_PRIMARY_ID && in[c]->value.type == ping_NRP_response) { + // Become primary. + self->primary = self->id; + lf_set_mode(Primary); + + lf_print(PRINTF_TIME ": Initial primary node %d received ping response on network %d. " + "Making switch %d the NRP.", lf_time_logical_elapsed(), self->id, c, in[c]->value.source + ); + self->NRP_network = c; + self->NRP_switch_id = in[c]->value.source; + // Notify the backup of the NRP. Destination 0 here means broadcast. + message_t message = {new_NRP, self->id, 0, in[c]->value.source}; + // Send new NRP message on all networks. + for (int i = 0; i < out_width; i++) lf_set(out[i], message); + } else if (in[c]->value.type == new_NRP) { + if (in[c]->value.payload != self->NRP_switch_id) { + // Message is not redundant (new_NRP sent on both networks). + // Become backup. Source of the message is the primary. + lf_print(PRINTF_TIME ": Waiting node %d received new NRP %d on network %d. " + "Becoming backup.", lf_time_logical_elapsed(), self->id, in[c]->value.payload, + c, in[c]->value.source + ); + self->primary = in[c]->value.source; + self->NRP_switch_id = in[c]->value.payload; + self->NRP_network = c; + lf_set_mode(Backup); + } + } + } + } + =} + } + + mode Primary { + timer heartbeat(0, heartbeat_period) + timer ping_NRP_timer(routine_ping_offset, heartbeat_period) + reaction(reset) {= + lf_print(PRINTF_TIME ": ---- Node %d becomes primary.", lf_time_logical_elapsed(), self->id); + =} + + reaction(node_fails) -> reset(Failed) {= + if(lf_time_logical_elapsed() > 0LL) lf_set_mode(Failed); + =} + + reaction(heartbeat) -> out {= + lf_print(PRINTF_TIME ": Primary node %d sends heartbeat on both networks.", + lf_time_logical_elapsed(), self->id + ); + message_t message = {heartbeat, self->id, 0, 0}; + for (int i = 0; i < out_width; i++) lf_set(out[i], message); + =} + + reaction(ping_NRP_timer) -> out, ping_timed_out {= + // Ping the NRP if there is one and there isn't a ping timeout pending. + if (self->NRP_switch_id != 0 && !self->ping_timeout_pending) { + lf_print(PRINTF_TIME ": Primary node %d pings NRP %d (routine).", + lf_time_logical_elapsed(), self->id, self->NRP_switch_id + ); + message_t ping = {ping_NRP, self->id, self->NRP_switch_id, 0}; + lf_set(out[self->NRP_network], ping); + self->ping_pending = true; + self->ping_timeout_pending = true; + lf_schedule(ping_timed_out, 0); + } + =} + + reaction(in) -> out, ping_timed_out {= + // Iterate over input channels. + for (int c = 0; c < in_width; c++) { + if (in[c]->is_present) { + if (in[c]->value.type == request_new_NRP) { + // Backup is asking for a new NRP. Invalidate current NRP. + self->NRP_switch_id = 0; + + // Switch networks. + if (self->NRP_network == 0) self->NRP_network = 1; + else self->NRP_network = 0; + + lf_print(PRINTF_TIME ": Primary node %d looking for new NRP on network %d.", + lf_time_logical_elapsed(), self->id, self->NRP_network + ); + message_t message = {ping_NRP, self->id, 0, 0}; + lf_set(out[self->NRP_network], message); + self->ping_pending = true; + self->ping_timeout_pending = true; + lf_schedule(ping_timed_out, 0); + } else if (in[c]->value.type == ping_NRP_response) { + lf_print(PRINTF_TIME ": Primary node %d received ping response on network %d. NRP is %d.", + lf_time_logical_elapsed(), self->id, c, in[c]->value.source + ); + self->ping_pending = false; + if (self->NRP_switch_id == 0) { + // This is a new NRP. + self->NRP_switch_id = in[c]->value.source; + self->NRP_network = c; + // Notify the backup of the NRP on the NRP's network. + message_t message = {new_NRP, self->id, 0, self->NRP_switch_id}; + lf_set(out[c], message); + lf_print(PRINTF_TIME ": Primary node %d notifies backup of new NRP %d on network %d.", + lf_time_logical_elapsed(), self->id, self->NRP_switch_id, c + ); + // NOTE: Should the primary get some confirmation from the backup? + } + } + } + } + =} + + reaction(ping_timed_out) -> out, ping_timed_out, reset(Failed) {= + self->ping_timeout_pending = false; + if (self->ping_pending) { + // Ping timed out. + self->ping_pending = false; + lf_print(PRINTF_TIME ": Primary node %d gets no response from ping.", + lf_time_logical_elapsed(), self->id + ); + if (self->NRP_switch_id == 0) { + // Failed to get a new NRP. Declare failure. + lf_set_mode(Failed); + } else { + // Invalidate current NRP. + self->NRP_switch_id = 0; + + // Switch networks. + if (self->NRP_network == 0) self->NRP_network = 1; + else self->NRP_network = 0; + + lf_print(PRINTF_TIME ": Primary node %d looking for new NRP on network %d.", + lf_time_logical_elapsed(), self->id, self->NRP_network + ); + message_t message = {ping_NRP, self->id, 0, 0}; + lf_set(out[self->NRP_network], message); + self->ping_pending = true; + self->ping_timeout_pending = true; + lf_schedule(ping_timed_out, 0); + } + } + =} + } + + // mode Primary + mode Backup { + timer t(heartbeat_period, heartbeat_period) + // NOTE: Paper says to SENDIMHERETOPRIMARY with "longer interval". + // Is this really necessary? + reaction(reset) {= + lf_print(PRINTF_TIME ": ---- Node %d becomes backup.", lf_time_logical_elapsed(), self->id); + =} + + reaction(node_fails) -> reset(Failed) {= + if(lf_time_logical_elapsed() > 0LL) lf_set_mode(Failed); + =} + + reaction(in) -> reset(Primary) {= + // Iterate over input channels. + for (int c = 0; c < in_width; c++) { + if (in[c]->is_present) { + if (in[c]->value.type == heartbeat) { + lf_print(PRINTF_TIME ": Backup node %d received heartbeat from node %d on network %d.", + lf_time_logical_elapsed(), self->id, in[c]->value.source, c + ); + self->heartbeats_missed[c] = 0; + } else if (in[c]->value.type == ping_NRP_response && in[c]->value.destination == self->id) { + // Got a response from the NRP to a ping we sent. + lf_print(PRINTF_TIME ": Backup node %d received ping response on network %d from NRP on switch %d.", + lf_time_logical_elapsed(), self->id, c, in[c]->value.source + ); + self->NRP_switch_id = in[c]->value.source; + // If there was a timeout on both networks that was not simultaneous, then + // we tried pinging the NRP before becoming primary. + if (self->become_primary_on_ping_response) { + lf_set_mode(Primary); + self->become_primary_on_ping_response = false; + } + self->ping_pending = false; + } else if (in[c]->value.type == new_NRP) { + // NOTE: Should ping the new NRP and send confirmation back to primary. + lf_print(PRINTF_TIME ": Backup node %d received new NRP %d on network %d.", + lf_time_logical_elapsed(), self->id, in[c]->value.payload, c + ); + self->NRP_network = c; + self->NRP_switch_id = in[c]->value.payload; + } + } + } + =} + + reaction(t) -> reset(Primary), out, ping_timed_out {= + if (self->heartbeats_missed[0] > self->max_missed_heartbeats + && self->heartbeats_missed[1] > self->max_missed_heartbeats) { + // Simultaneous heartbeat misses. + // In the paper, this is tmoAllNotSimul. + // For the tmoAllSimul optimization in the paper, we assume that if + // self->heartbeats_missed[0] == self->heartbeats_missed[1], then most likely, it is + // the primary that failed, and not the network, so can immediately become the primary. + // Otherwise, it is possible that one network failed, and then the other failed, in which + // case, we may have a partitioned network. + lf_print(PRINTF_TIME ": **** Backup node %d detects missing heartbeats on both networks.", + lf_time_logical_elapsed(), self->id + ); + if (self->heartbeats_missed[0] == self->heartbeats_missed[1]) { + lf_print(PRINTF_TIME ": **** Missing heartbeats on both networks were simultaneous. " + "Assume the primary failed.", + lf_time_logical_elapsed() + ); + lf_set_mode(Primary); + } else if (self->NRP_switch_id != 0) { + // Ping the NRP because if we can't access it, we are on the wrong side of + // a network partition and could end up with two primaries. + message_t message = {ping_NRP, self->id, self->NRP_switch_id, 0}; + lf_set(out[self->NRP_network], message); + // Wait for a response before becoming primary. + self->become_primary_on_ping_response = true; + self->ping_pending = true; + self->ping_timeout_pending = true; + lf_schedule(ping_timed_out, 0); + } else { + lf_print_warning(PRINTF_TIME "**** Do not know which switch is the NRP! Cannot become primary.", + lf_time_logical_elapsed() + ); + } + self->heartbeats_missed[0] = 0; // Prevent detecting again immediately. + self->heartbeats_missed[1] = 0; + } else if (self->heartbeats_missed[0] > self->max_missed_heartbeats + || self->heartbeats_missed[1] > self->max_missed_heartbeats) { + // Heartbeat missed on one network but not yet on the other. + // Ping the NRP to make sure we retain access to it so that we can be an effective backup. + // This corresponds to tmoSomeNotAll in the paper. + lf_print(PRINTF_TIME ": **** Backup node %d detects missing heartbeats on one network.", + lf_time_logical_elapsed(), self->id + ); + // Ping the NRP. + message_t message = {ping_NRP, self->id, self->NRP_switch_id, 0}; + if (!self->ping_pending && !self->ping_timeout_pending && self->NRP_switch_id != 0) { + lf_set(out[self->NRP_network], message); + lf_print(PRINTF_TIME ": Backup node %d pings NRP on network %d, switch %d", + lf_time_logical_elapsed(), self->id, self->NRP_network, self->NRP_switch_id + ); + self->ping_pending = true; + self->ping_timeout_pending = true; + lf_schedule(ping_timed_out, 0); + } + } + // Increment the counters so if they are not reset to 0 by the next time, + // we detect the missed heartbeat. + self->heartbeats_missed[0]++; + self->heartbeats_missed[1]++; + =} + + reaction(ping_timed_out) -> out, new_NRP_request_timed_out, reset(Failed) {= + self->ping_timeout_pending = false; + if (self->ping_pending) { + // Ping timed out. + lf_print(PRINTF_TIME ": Backup node %d gets no response from ping.", lf_time_logical_elapsed(), self->id); + if (self->NRP_switch_id != 0) { + // Send request for new NRP on the other network. + lf_print(PRINTF_TIME ": Backup node %d requests new NRP.", lf_time_logical_elapsed(), self->id); + + // Invalidate current NRP. + self->NRP_switch_id = 0; + + // Switch networks. + if (self->NRP_network == 0) self->NRP_network = 1; + else self->NRP_network = 0; + + message_t message = {request_new_NRP, self->id, self->primary, 0}; + lf_set(out[self->NRP_network], message); + + lf_schedule(new_NRP_request_timed_out, 0); + } else { + // Failed to connect to new NRP. + lf_set_mode(Failed); + } + self->ping_pending = false; + } + =} + + reaction(new_NRP_request_timed_out) {= + if (self->NRP_switch_id == 0) { + lf_print(PRINTF_TIME ": Backup node %d new NRP request timed out. Will not function as backup.", + lf_time_logical_elapsed(), self->id + ); + if (self->become_primary_on_ping_response) { + lf_print(PRINTF_TIME ": Network is likely partitioned. Remaining as (non-functional) backup.", + lf_time_logical_elapsed() + ); + self->become_primary_on_ping_response = false; + } + } + =} + } + + mode Failed { + reaction(reset) {= + lf_print(PRINTF_TIME ": #### Node %d fails.", lf_time_logical_elapsed(), self->id); + =} + } +} + +/** + * Switch with two interfaces. When a ping_NRP message arrives on either interface, if the + * destination matches the ID of this switch or the destination is 0, then the switch responds on + * the same interface with a ping_NRP_response message. When any other message arrives on either + * interface, the switch forwards a copy of the message to the other interface. If any two messages + * would be simultaneous on an output, one will be sent one microstep later. + */ +reactor Switch( + id: int = 0, + // For testing. 0 for no failure. + fails_at_time: time = 0) { + input in1: message_t + @side("east") + input in2: message_t + @side("west") + output out1: message_t + output out2: message_t + + logical action pending_out1: message_t + logical action pending_out2: message_t + + timer switch_fails(fails_at_time) + + initial mode Working { + reaction(switch_fails) -> reset(Failed) {= + if(lf_time_logical_elapsed() > 0LL) lf_set_mode(Failed); + =} + + reaction(pending_out1) -> out1 {= + lf_set(out1, pending_out1->value); + =} + + reaction(pending_out2) -> out2 {= + lf_set(out2, pending_out2->value); + =} + + reaction(in1, in2) -> out1, out2, pending_out1, pending_out2 {= + if (in1->is_present) { + if (in1->value.type == ping_NRP) { + if (in1->value.destination == self->id || in1->value.destination == 0) { + lf_print(PRINTF_TIME ": ==== Switch %d pinged by node %d. Responding.", lf_time_logical_elapsed(), self->id, in1->value.source); + // Respond to the ping. + message_t message = {ping_NRP_response, self->id, in1->value.source}; + if (!out1->is_present) { + lf_set(out1, message); + } else { + lf_schedule_copy(pending_out1, 0, &message, 1); + } + } else { + // Forward the ping. + if (!out2->is_present) { + lf_set(out2, in1->value); + } else { + lf_schedule_copy(pending_out2, 0, &in1->value, 1); + } + } + } else { + // Forward the message. + if (!out2->is_present) { + lf_set(out2, in1->value); + } else { + lf_schedule_copy(pending_out2, 0, &in1->value, 1); + } + } + } + if (in2->is_present) { + if (in2->value.type == ping_NRP) { + if (in2->value.destination == self->id) { + lf_print(PRINTF_TIME ": ==== Switch %d pinged by node %d. Responding.", lf_time_logical_elapsed(), self->id, in2->value.source); + // Construct a response to the ping. + message_t message = {ping_NRP_response, self->id, in2->value.source}; + // Respond to the ping if out2 is available. + if (!out2->is_present) { + lf_set(out2, message); + } else { + lf_schedule_copy(pending_out2, 0, &message, 1); + } + } else { + // Forward the ping to out1 if out1 is available. + if (!out1->is_present) { + lf_set(out1, in2->value); + } else { + lf_schedule_copy(pending_out1, 0, &in2->value, 1); + } + } + } else { + // Forward the message if out1 is available. + if (!out1->is_present) { + lf_set(out1, in2->value); + } else { + lf_schedule_copy(pending_out1, 0, &in2->value, 1); + } + } + } + =} + } + + mode Failed { + reaction(reset) {= + lf_print(PRINTF_TIME ": ==== Switch %d fails.", lf_time_logical_elapsed(), self->id); + =} + } +} + +federated reactor(heartbeat_period: time = 1 s, delay: time = 1 ms) { + node1 = new Node(heartbeat_period=heartbeat_period, id=1, fails_at_time = 10 s) + switch1 = new Switch(id=1, fails_at_time = 3 s) + switch3 = new Switch(id=3) + + node2 = new Node(heartbeat_period=heartbeat_period, id=2, fails_at_time = 15 s) + switch2 = new Switch(id=2) + switch4 = new Switch(id=4) + + node1.out -> switch1.in1, switch3.in1 after delay + switch1.out1, switch3.out1 -> node1.in after delay + + switch1.out2 -> switch2.in2 after delay + switch2.out2 -> switch1.in2 after delay + + switch2.out1, switch4.out1 -> node2.in after delay + node2.out -> switch2.in1, switch4.in1 after delay + + switch3.out2 -> switch4.in2 after delay + switch4.out2 -> switch3.in2 after delay +} diff --git a/examples/C/src/leader-election/NRP_FD_Partitioning.lf b/examples/C/src/leader-election/NRP_FD_Partitioning.lf new file mode 100644 index 00000000..3a046333 --- /dev/null +++ b/examples/C/src/leader-election/NRP_FD_Partitioning.lf @@ -0,0 +1,37 @@ +/** + * This version of NRP_FD partitions the network and shows that the protocol prevents the backup + * from becoming primary, thereby preventing two primaries. + * + * @author Edward A. Lee + * @author Marjan Sirjani + */ +// This version +target C { + tracing: true, + timeout: 20 s +} + +import Switch, Node from "NRP_FD.lf" + +federated reactor(heartbeat_period: time = 1 s, delay: time = 1 ms) { + node1 = new Node(heartbeat_period=heartbeat_period, id=1, fails_at_time = 15 s) + node2 = new Node(heartbeat_period=heartbeat_period, id=2, fails_at_time = 15 s) + + switch1 = new Switch(id=1, fails_at_time = 3 s) + switch2 = new Switch(id=2) + switch3 = new Switch(id=3) + // Failure of switch4 will partition the network. + switch4 = new Switch(id=4, fails_at_time = 10 s) + + node1.out -> switch1.in1, switch3.in1 after delay + switch1.out1, switch3.out1 -> node1.in after delay + + switch1.out2 -> switch2.in2 after delay + switch2.out2 -> switch1.in2 after delay + + switch2.out1, switch4.out1 -> node2.in after delay + node2.out -> switch2.in1, switch4.in1 after delay + + switch3.out2 -> switch4.in2 after delay + switch4.out2 -> switch3.in2 after delay +} diff --git a/examples/C/src/leader-election/NRP_FD_PrimaryFails.lf b/examples/C/src/leader-election/NRP_FD_PrimaryFails.lf new file mode 100644 index 00000000..c9ed2969 --- /dev/null +++ b/examples/C/src/leader-election/NRP_FD_PrimaryFails.lf @@ -0,0 +1,37 @@ +/** + * This version of NRP_FD simply has the primary (node1) failing after 5 seconds and the backup + * (node2) failing at at 15s. The backup detects simultaneous loss of the heartbeat on both networks + * and hence assumes that the primary has failed rather than there being a network failure. Switch 1 + * remains the NRP. + * + * @author Edward A. Lee + * @author Marjan Sirjani + */ +target C { + tracing: true, + timeout: 20 s +} + +import Switch, Node from "NRP_FD.lf" + +federated reactor(heartbeat_period: time = 1 s, delay: time = 1 ms) { + node1 = new Node(heartbeat_period=heartbeat_period, id=1, fails_at_time = 5 s) + node2 = new Node(heartbeat_period=heartbeat_period, id=2, fails_at_time = 15 s) + + switch1 = new Switch(id=1) + switch2 = new Switch(id=2) + switch3 = new Switch(id=3) + switch4 = new Switch(id=4) + + node1.out -> switch1.in1, switch3.in1 after delay + switch1.out1, switch3.out1 -> node1.in after delay + + switch1.out2 -> switch2.in2 after delay + switch2.out2 -> switch1.in2 after delay + + switch2.out1, switch4.out1 -> node2.in after delay + node2.out -> switch2.in1, switch4.in1 after delay + + switch3.out2 -> switch4.in2 after delay + switch4.out2 -> switch3.in2 after delay +} diff --git a/examples/C/src/leader-election/README.md b/examples/C/src/leader-election/README.md new file mode 100644 index 00000000..d2cce2c9 --- /dev/null +++ b/examples/C/src/leader-election/README.md @@ -0,0 +1,36 @@ +# Leader Election + +These federated programs implement redundant fault-tolerant systems where a primary node, if and when it fails, is replaced by a backup node. The HeartbeatBully example is described in this paper: + +> B. Johansson, M. Rågberger, A. V. Papadopoulos and T. Nolte, "Heartbeat Bully: Failure Detection and Redundancy Role Selection for Network-Centric Controller," IECON 2020 The 46th Annual Conference of the IEEE Industrial Electronics Society, Singapore, 2020, pp. 2126-2133, [DOI: 10.1109/IECON43393.2020.9254494](https://doi.org/10.1109/IECON43393.2020.9254494). + +The NRP examples extend the algorithm to reduce the likelihood of getting multiple primaries when the network becomes partitioned. The NRP protocol is described in this paper: + +> B. Johansson, M. Rågberger, A. V. Papadopoulos, and T. Nolte, "Consistency Before Availability: Network Reference Point based Failure Detection for Controller Redundancy," Emerging Technologies and Factory Automation (ETFA), 12-15 September 2023, [DOI:10.1109/ETFA54631.2023.10275664](https://doi.org/10.1109/ETFA54631.2023.10275664) + +The key idea in the NRP protocol is that when a backup fails to detect the heartbeats of a primary node, it becomes primary only if it has access to Network Reference Point (NRP), which is a point in the network. This way, if the network becomes partitioned, only a backup that is on the side of the partition that still has access to the NRP can become a primary. If a primary loses access to the NRP, then it relinquishes its primary role because it is now on the wrong side of a network partition. A backup on the right side of the partition will take over. The "FD" in the names of the programs stands for "fault detection." + +## Prerequisite + +To run these programs, you are required to first [install the RTI](https://www.lf-lang.org/docs/handbook/distributed-execution?target=c#installation-of-the-rti) (the Run-Time Infrastructure), which handles the coordination. + +## Examples + + + + + + + + + + + + + + + + + + +
    Heartbeat Bully HeartbeatBully.lf : Basic leader electrion protocol called "heartbeat bully".
    NRP_FD NRP_FD.lf : Extension using a network reference point (NRP) to help prevent multiple primaries. This version has switch1 failing at 3s, node1 failing at 10s, and node2 failing at 15s.
    NRP_FD_PrimaryFails NRP_FD_PrimaryFails.lf : This version has the primary (node1) failing after 5 seconds and the backup (node2) failing at at 15s. The backup detects simultaneous loss of the heartbeat on both networks and hence assumes that the primary has failed rather than there being a network failure. Switch 1 remains the NRP.
    NRP_FD_Partitioning NRP_FD_Partitioning.lf : This version partitions the network and shows that the protocol prevents the backup from becoming primary, thereby preventing two primaries.
    diff --git a/examples/C/src/leader-election/img/HeartbeatBully.png b/examples/C/src/leader-election/img/HeartbeatBully.png new file mode 100644 index 00000000..54ed8551 Binary files /dev/null and b/examples/C/src/leader-election/img/HeartbeatBully.png differ diff --git a/examples/C/src/leader-election/img/NRP_FD.png b/examples/C/src/leader-election/img/NRP_FD.png new file mode 100644 index 00000000..026cb609 Binary files /dev/null and b/examples/C/src/leader-election/img/NRP_FD.png differ diff --git a/examples/C/src/leader-election/img/NRP_FD_Partitioning.png b/examples/C/src/leader-election/img/NRP_FD_Partitioning.png new file mode 100644 index 00000000..7604b7fb Binary files /dev/null and b/examples/C/src/leader-election/img/NRP_FD_Partitioning.png differ diff --git a/examples/C/src/leader-election/img/NRP_FD_PrimaryFails.png b/examples/C/src/leader-election/img/NRP_FD_PrimaryFails.png new file mode 100644 index 00000000..8cff396e Binary files /dev/null and b/examples/C/src/leader-election/img/NRP_FD_PrimaryFails.png differ diff --git a/examples/C/src/lib/ServerUI.lf b/examples/C/src/lib/ServerUI.lf index 2264d618..ec838648 100644 --- a/examples/C/src/lib/ServerUI.lf +++ b/examples/C/src/lib/ServerUI.lf @@ -8,7 +8,7 @@ * * @param initial_file Path to the initial HTML file to serve, relative to the source directory of * the main reactor. Defaults to "page.html". - * @param hostport The host port number, which defults to 8080. + * @param hostport The host port number, which defaults to 8080. * * @author Edward A. Lee */ @@ -17,13 +17,15 @@ target C { } preamble {= - #ifndef BROWSERUI_H - #define BROWSERUI_H + #ifndef SERVERUI_H + #define SERVERUI_H #include "util.h" // Defines lf_print() #include "platform.h" // Defines lf_thread_t, etc. #include #include + #include // Defines errno + #include // Defines strerror() #include // Defines read(), close() #include // Defines strchr() #include // Defines dirname() @@ -35,7 +37,7 @@ preamble {= uint16_t hostport; // The port to use for HTTP access. char* initial_page; // Initial web page contents. } browser_ui_t; - #endif // BROWSERUI_H + #endif // SERVERUI_H =} reactor ServerUI(initial_file: string = "page.html", hostport: uint16_t = 8080) { @@ -87,18 +89,27 @@ reactor ServerUI(initial_file: string = "page.html", hostport: uint16_t = 8080) lf_print_error_and_exit("Error accepting connection."); } - char buffer[1024] = {0}; - read(browser_ui->client_socket, buffer, 1024); + char buffer[2048] = {0}; + ssize_t bytes_read = read(browser_ui->client_socket, buffer, 2047); // Ensure null terminator. + if (bytes_read == 0) continue; + if (bytes_read < 0) { + lf_print_warning("Error %d reading socket: %s", errno, strerror(errno)); + continue; + } + + lf_print("Received: '%s'", buffer); // The response depends on the path part of the request. - const char *start_of_path = strchr(buffer, ' ') + 1; - if (start_of_path != NULL && strncmp("/ ", start_of_path, 2) != 0) { - const char *end_of_path = strchr(start_of_path, ' ') + 1; - size_t length = end_of_path - start_of_path; - char* path = (char*)malloc(length + 1); - strncpy(path, start_of_path, length); - path[length] = '\0'; - lf_schedule_value(browser_ui->req_action, 0, path, length + 1); + const char *start_of_path = strchr(buffer, ' '); + if (start_of_path != NULL && strncmp("/ ", start_of_path + 1, 2) != 0) { + const char *end_of_path = strchr(start_of_path + 1, ' ') + 1; + if (end_of_path != NULL && end_of_path - start_of_path > 1) { + size_t length = end_of_path - start_of_path - 1; + char* path = (char*)malloc(length + 1); + strncpy(path, start_of_path + 1, length); + path[length] = '\0'; + lf_schedule_value(browser_ui->req_action, 0, path, length + 1); + } } else { // Default is to write initial page. write(browser_ui->client_socket, html_header, strlen(html_header)); diff --git a/examples/C/src/lib/WebSocketCmake.txt b/examples/C/src/lib/WebSocketCmake.txt index bc2089c8..851a57bc 100644 --- a/examples/C/src/lib/WebSocketCmake.txt +++ b/examples/C/src/lib/WebSocketCmake.txt @@ -1,3 +1,5 @@ find_package(libwebsockets REQUIRED) # Finds the libwebsockets library -target_link_libraries( ${LF_MAIN_TARGET} websockets ) # Links the websockets library +# Note that the PUBLIC keyword is needed otherwise CMake will issue an error +# saying that you must use either all-keywords or all-plain, and the keywords are previously used. +target_link_libraries( ${LF_MAIN_TARGET} PUBLIC websockets ) # Links the websockets library diff --git a/examples/C/src/lib/WebSocketServer.lf b/examples/C/src/lib/WebSocketServer.lf index b96db945..7071d30d 100644 --- a/examples/C/src/lib/WebSocketServer.lf +++ b/examples/C/src/lib/WebSocketServer.lf @@ -1,23 +1,42 @@ /** * A web socket server enabling a user interface realized in the browser. This creates a web server - * that listens on a port (default 8000) for web socket connections. + * that listens on a port (default 8080) for web socket connections. The intended usage is that you + * create a client in an HTML/JavaScript file that connects to this server and provides a + * browser-based user interface. * - * When a connection is established with a client, an output is produced on the connected port that - * is a struct with a unique wsi (web socket interface) for the client and a boolean indicating - * whether the connection is being opened or closed. The wsi can be used to provide input at the - * send port that will target this specific client. + * If an `initial_file` parameter value is provided, it is expected to be a fully-qualified path to + * a file that this server will serve upon receiving an HTTP request. This can be used to provide + * the initial HTML (with embedded JavaScript) that will then establish a web socket connection to + * the running LF program. To specify a file located in the same directory as your source LF file, + * you can use a syntax like this: + * ``` + * s = new WebSocketServer( + * initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "filename.html" =} + * ) + * ``` + + * + * When a connection is established with a client, an output is produced on the `connected` port + * that is a struct with a unique `wsi` (web socket interface) for the client and a boolean + * indicating whether the connection is being opened or closed. The `wsi` can be used to provide + * input at the send port that will target this specific client. * * To send messages to a client, construct a dynamically allocated struct of type - * web_socket_message_t, set its wsi field to the value provided by the connected output, and set - * its message and length. Only strings can be sent. + * `web_socket_message_t`, set its `wsi` field to the value provided by the connected output, and + * set its `message` and `length`. The sending data type is an array of `char`, so any byte array + * can be sent. If you sending and receiving strings, consider using the simpler wrapper + * `WebSocketServerString`. + * + * When a message is received from a client, a struct of type `web_socket_message_t` will be + * produced on the `received` output port. You can use the `wsi` field to determine which client + * sent the message. * - * When a message is received from a client, a struct of type web_socket_message_t will be produced - * on the received output port. You can use the wsi field to determine which client sent the - * message. + * You can limit the number of clients by setting the `max_clients` parameter. It defaults to 0, + * which means there is no limit. A common case for an embedded application might be 1 to ensure + * that only one client connects to your application. * - * You can limit the number of clients by setting the max_clients parameter. It defaults to 0, which - * means there is no limit. A common case for an embedded application might be 1 to ensure that only - * one client connects to your application. + * To broadcast a message to all active clients, simply provide an input where the `wsi` field is + * `NULL`. * * This uses the libwebsockets (see API documentation and ). To install on MacOS, we recommending using brew: *
     brew install libwebsockets
      * 
    This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be - * linked to providing the {@code -lwebsockets} compile option. + * linked to using the {@code -lwebsockets} compile option or the {@code WebSocketCmake.txt} Cmake + * include file. * - * There are a number of limitations: - *
      - *
    1. **FIXME:** This should use the secure sockets API in libwebsockets to get SSL. - *
    2. **FIXME:** This currently only supports sending and receiving text. - *
    + * The data conveyed can be any byte array. In case a received value is a string that is not null + * terminated, this reactor appends a null character after the message payload. It does not include + * this null character in the length field of the output struct, but rather just reports the length + * as reported by the incoming message. If the `binary` parameter is set to true, then JavaScript at + * the receiving end will get a Blob. Otherwise, it gets text. * - * @param hostport The host port number, which defults to 8000. - * @max_clients Maximum number of clients, or 0 for no upper bound. + * A key limitation is that this should use the secure sockets API in libwebsockets to get SSL. + * + * @param hostport The host port number, which defaults to 8080. + * @param initial_file If non-NULL and non-empty, a file name to serve to HTTP requests. + * @param max_clients Maximum number of clients, or 0 for no upper bound. * * @author Edward A. Lee */ @@ -48,16 +71,33 @@ preamble {= #define WEBSOCKET_H #include "util.h" // Defines lf_print() #include "platform.h" // Defines lf_thread_t, etc. + #include "hashset/hashset.h" // For storing web socket instances that are connected. #include + /** + * A web socket string message together with its web socket instance. + * This needs a destructor and copy constructor because the message + * is assumed to be in allocated memory. + */ + typedef struct web_socket_message_t { + struct lws* wsi; // Web socket instance. + size_t length; + void* message; + bool binary; + struct web_socket_message_t* next; // Pointer to the next message in the list or NULL for end. + } web_socket_message_t; + typedef struct server_status_t { void* connected_action; // Action to notify of changes in connected status. - void* received_action; // Action to notify of messages received. + void* received_action; // Action to notify of messages received. struct lws_context* context; // The context. - int max_clients; // Maximum number of clients. - int* client_count; // Pointer to the client_count state variable. - bool running; // Indicator that the listening thread is running. + int max_clients; // Maximum number of clients. + int* client_count; // Pointer to the client_count state variable. + bool running; // Indicator that the listening thread is running. + web_socket_message_t* pending_messages; // Head of a list of pending messages to send. + lf_mutex_t* mutex; // Mutex for modifying this struct. + string initial_file; // File to serve in response to HTTP requests. } server_status_t; /** @@ -70,17 +110,6 @@ preamble {= bool connected; } web_socket_instance_t; - /** - * A web socket string message together with its web socket instance. - * This needs a destructor and copy constructor because the message - * is assumed to be in allocated memory. - */ - typedef struct web_socket_message_t { - struct lws* wsi; // Web socket instance. - size_t length; - char* message; - } web_socket_message_t; - /** Destructor for an instance of web_socket_message_t. */ void web_socket_message_destructor(void* message); @@ -90,9 +119,13 @@ preamble {= #endif // WEBSOCKET_H =} -reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { +reactor WebSocketServer( + hostport: int = 8080, + initial_file: string = {= NULL =}, + max_clients: int = 0) { output connected: web_socket_instance_t output received: web_socket_message_t* + input send: web_socket_message_t* physical action connected_action: web_socket_instance_t @@ -100,14 +133,19 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { state status: server_status_t state client_count: int = 0 + state connected_instances: hashset_t = {= NULL =} preamble {= // Thread handling incoming messages. + // All lws calls except lws_cancel_service must occur in this thread. void* websocket_thread(void* args) { server_status_t* status = (server_status_t*)args; + while(status->running) { + // Invoke any pending callbacks. // According to the docs, the timeout argument is ignored. - lws_service(status->context, 50); + // Nevertheless, set to 100ms. + lws_service(status->context, 100); } lws_context_destroy(status->context); return NULL; @@ -123,6 +161,13 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { LF_PRINT_LOG("HTTP callback invoked with reason: %d", reason); web_socket_instance_t ws_instance; switch(reason) { + case LWS_CALLBACK_HTTP: + // HTTP request that is not asking for an upgrade. + // If the initial_file parameter is non-empty, serve that file. + if (status->initial_file != NULL && strlen(status->initial_file) > 0) { + lws_serve_http_file(wsi, status->initial_file, "text/html", NULL, 0); + } + break; case LWS_CALLBACK_WSI_CREATE: LF_PRINT_LOG("**** Web socket connection requested."); break; @@ -131,6 +176,9 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { break; case LWS_CALLBACK_HTTP_CONFIRM_UPGRADE: + // NOTE: We do not need to lock status mutex to check and update client_count + // because it is only checked and updated in this websocket_thread. + // Check against maximum number of connections. if (status->max_clients > 0 && *status->client_count >= status->max_clients) { // Deny the connection. @@ -138,6 +186,7 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { // Increment the client count past the maximum because it will be // decremented when this closes and the browser will retry. *status->client_count = *status->client_count + 1; + return 1; } @@ -175,6 +224,7 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { lf_schedule_copy(status->connected_action, 0, &ws_instance, 1); break; default: + LF_PRINT_LOG("*** Unhandled callback with lws_callback_reasons %d.", reason); break; } return 0; @@ -185,6 +235,21 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { LF_PRINT_LOG("WS callback invoked with reason: %d", reason); server_status_t* status = (server_status_t*)lws_context_user(lws_get_context(wsi)); switch(reason) { + case LWS_CALLBACK_EVENT_WAIT_CANCELLED: + // lws_service() blocking call interrupted. Check for messages to send. + if (lf_mutex_lock(status->mutex)) { + lf_print_error("Failed to lock mutex in WebSocketServer."); + return 0; + } + + if (status->pending_messages) { + lws_callback_on_writable(status->pending_messages->wsi); + } + + if (lf_mutex_unlock(status->mutex)) { + lf_print_error("Failed to unlock mutex in WebSocketServer."); + } + break; case LWS_CALLBACK_RECEIVE: if (len > 0) { LF_PRINT_LOG("**** Server received WS message."); @@ -192,20 +257,73 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { web_socket_message_t* received = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); received->wsi = wsi; - // Message is not NULL terminated, so created a NULL-terminated version. - char* string = (char*)malloc((len + 1) * sizeof(char)); - strncpy(string, (char*)in, len); - string[len] = 0; - received->length = len + 1; - received->message = string; + // To protect against non-null-terminated strings, add a null character beyond the length. + void* payload = malloc((len + 1) * sizeof(char)); + memcpy(payload, in, len); + ((char*)payload)[len] = '\0'; + received->length = len; + received->message = payload; + received->next = NULL; + received->binary = true; // Treat all received data as binary. + // Carry the null terminator in the action payload, just in case. lf_schedule_value(status->received_action, 0, received, len + 1); } break; + case LWS_CALLBACK_SERVER_WRITEABLE: + // Websocket has become writable. See whether there are pending + // messages to send. This requires locking the status mutex. + if (lf_mutex_lock(status->mutex)) { + lf_print_error("Failed to lock mutex in WebSocketServer."); + return 0; + } + + web_socket_message_t* to_send = status->pending_messages; + if (to_send) { + // There is a message to send. Remove it from the list. + status->pending_messages = to_send->next; + + // Send it. + int length = to_send->length; + // The buffer needs LWS_PRE bytes _before_ the message. + // Do not include the null terminator, because this makes JSON unable to parse it. + unsigned char buffer[LWS_PRE + length + 1]; // +1 for possible null terminator. + memcpy(&buffer[LWS_PRE], to_send->message, length); + int result; + if (to_send->binary) { + result = lws_write(to_send->wsi, &buffer[LWS_PRE], length, LWS_WRITE_BINARY); + } else { + // Check for null terminator. + if (buffer[LWS_PRE + length - 1] != '\0') { + // Null terminator is missing. + buffer[LWS_PRE + length] = '\0'; + length += 1; + } + result = lws_write(to_send->wsi, &buffer[LWS_PRE], length, LWS_WRITE_TEXT); + } + if (result < length) { + lf_print_warning("Send on web socket failed. Message send is incomplete."); + } + // Free the memory for the pending send. + web_socket_message_destructor(to_send); + + // If there is another message, request another callback. + if (status->pending_messages) { + lws_callback_on_writable(status->pending_messages->wsi); + } + } + + if (lf_mutex_unlock(status->mutex)) { + lf_print_error("Failed to unlock mutex in WebSocketServer."); + } + + break; + // Do we need to handle LWS_CALLBACK_CLOSED? // Seems to be handled in the HTTP callback. default: + LF_PRINT_LOG("*** Unhandled callback with lws_callback_reasons %d.", reason); break; } return 0; @@ -214,26 +332,36 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { void web_socket_message_destructor(void* message) { free(((web_socket_message_t*)message)->message); free(message); + // Do not free the wsi. } + // Argument and return type is web_socket_message_t*. void* web_socket_message_copy_constructor(void* message) { web_socket_message_t* cast = (web_socket_message_t*)message; web_socket_message_t* result = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); size_t length = cast->length; - char* copy = (char*)malloc(length * sizeof(char) + 1); - result->message = strncpy(copy, cast->message, length); - result->message[length] = 0; + void* copy = malloc(length * sizeof(char)); + result->message = memcpy(copy, cast->message, length); result->wsi = cast->wsi; + result->length = length; + result->binary = cast->binary; + result->next = NULL; return result; } =} - reaction(startup) -> connected_action, received_action {= + reaction(startup) send -> connected_action, received_action {= // The receiving thread dynamically allocates memory for messages. // Set the destructor and copy constructor. lf_set_destructor(received_action, web_socket_message_destructor); lf_set_copy_constructor(received_action, web_socket_message_copy_constructor); + // Assume the input is dynamically allocated, including its message field. + lf_set_destructor(send, web_socket_message_destructor); + lf_set_copy_constructor(send, web_socket_message_copy_constructor); + + self->connected_instances = hashset_create(8); // Default capacity for eight instances. + struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); info.port = self->hostport; @@ -254,6 +382,12 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { // To get callbacks to be passed a pointer to the status struct: info.user = &self->status; + // Callbacks will need to acquire a mutex to modify the status struct pending_messages field. + self->status.mutex = (lf_mutex_t*)calloc(1, sizeof(lf_mutex_t)); + if (lf_mutex_init(self->status.mutex)) { + lf_print_error_and_exit("Failed to initialize mutex in WebSocketServer."); + } + self->status.context = lws_create_context(&info); if (!self->status.context) { lf_print_error_and_exit("Failed to create server for web sockets."); @@ -264,39 +398,90 @@ reactor WebSocketServer(hostport: int = 8000, max_clients: int = 0) { self->status.max_clients = self->max_clients; self->status.client_count = &self->client_count; + self->status.initial_file = self->initial_file; + self->status.running = true; lf_thread_t listener; lf_thread_create(&listener, &websocket_thread, &self->status); - self->status.running = true; =} reaction(received_action) -> received {= lf_set_token(received, received_action->token); =} + reaction(connected_action) -> connected {= + lf_set(connected, connected_action->value); + if (connected_action->value.connected) { + hashset_add(self->connected_instances, connected_action->value.wsi); + } else { + hashset_remove(self->connected_instances, connected_action->value.wsi); + } + =} + reaction(send) {= - // NOTE: This send must be before the reaction to connected_action - // because the latter could cause a disconnection. + // NOTE: This send cannot be before the reaction to connected_action + // because we will get a causality loop. if(send->value->message == NULL) { - lf_print_error("NULL message received."); - } else { - int length = strlen(send->value->message); - // The buffer needs LWS_PRE bytes _before_ the message. - // Do not include the null terminator, because this make JSON unable to parse it. - char buffer[LWS_PRE + length]; - strcpy(buffer + LWS_PRE, send->value->message); - int result = lws_write(send->value->wsi, (unsigned char*)(buffer + LWS_PRE), length, LWS_WRITE_TEXT); - if (result < length) { - lf_print_warning("Send on web socket failed. Dropping message."); + lf_print_error("Cannot send NULL message."); + } else if (send->value->wsi == NULL) { + // Broadcast requested. + // There does not appear to be a way to iterate over hashset contents. + // So we iterate over the entire hashset storage and skip empty slots. + for (int i = 0; i < self->connected_instances->capacity; i++) { + struct lws* instance = (struct lws*) self->connected_instances->items[i]; + if (instance == NULL || instance == (struct lws*)1) continue; // Empty or deleted items. + web_socket_message_t to_send; + to_send.wsi = instance; + to_send.length = send->value->length; + to_send.message = send->value->message; + to_send.binary = send->value->binary; + to_send.next = NULL; + send_message(&to_send); } + } else if (hashset_is_member(self->connected_instances, send->value->wsi)) { + send_message(send->value); + } else { + lf_print_warning("Web socket no longer connected. Dropping message."); } =} - reaction(connected_action) -> connected {= - lf_set(connected, connected_action->value); + method send_message(to_send: web_socket_message_t*): int {= + // Cannot actually send the message here for two reasons: + // 1. websocketlib is not thread safe, so the write needs to occur in the one thread making calls. + // 2. It is not safe to write until the socket is ready for a write. + // Hence, we append the message to list of pending messages and wait for a callback that + // the socket is ready for a write. + + // Copy the message and append to the context list. + web_socket_message_t* copy = (web_socket_message_t*)web_socket_message_copy_constructor(to_send); + + // Acquire the status mutex before putting on the pending messages list. + if (lf_mutex_lock(self->status.mutex)) { + lf_print_error("Failed to lock WebSocketServer mutex."); + return -1; + } + + // Scroll to the end of the list of pending messages. + web_socket_message_t** existing = &(self->status.pending_messages); + while (*existing != NULL) { + existing = &((*existing)->next); + } + *existing = copy; + + // Interrupt lws_service(). + lws_cancel_service(self->status.context); + + if (lf_mutex_unlock(self->status.mutex)) { + lf_print_error("Failed to unlock WebSocketServer mutex."); + return -1; + } + return 0; =} reaction(shutdown) {= self->status.running = false; + if (self->connected_instances != NULL) { + hashset_destroy(self->connected_instances); + } =} } diff --git a/examples/C/src/lib/WebSocketServerString.lf b/examples/C/src/lib/WebSocketServerString.lf new file mode 100644 index 00000000..1cfd6f4d --- /dev/null +++ b/examples/C/src/lib/WebSocketServerString.lf @@ -0,0 +1,106 @@ +/** + * A web socket server enabling a user interface realized in the browser. This creates a web server + * that listens on a port (default 8080) for web socket connections. The intended usage is that you + * create a client in an HTML/JavaScript file that connects to this server and provides a + * browser-based user interface. + * + * This version is simpler to use than WebSocketServer. It is limited to a single connection at a + * time and to string-valued messages. + * + * If an `initial_file` parameter value is provided, it is expected to be a fully-qualified path to + * a file that this server will serve upon receiving an HTTP request. This can be used to provide + * the initial HTML (with embedded JavaScript) that will then establish a web socket connection to + * the running LF program. To specify a file located in the same directory as your source LF file, + * you can use a syntax like this: + * ``` + * s = new WebSocketServer( + * initial_file = {= LF_SOURCE_DIRECTORY LF_FILE_SEPARATOR "filename.html" =} + * ) + * ``` + + * + * When a connection is established with a client, an output with value `true` is produced on the + * `connected` port. A value `false` is produced when the client disconnects. + * + * To send messages to a client, simply provide a string on one of the `in_dynamic` or `in_static` + * input ports. The former is used for dynamically-allocated strings (type `char*`) and the latter + * for statically allocated strings (type `string`). If both are provided at the same tag, then only + * the `in_dynamic` message will be sent. + * + * When a message is received from a client, a string will be produced on the `received` output + * port. The type of this port is `char*`, indicating a dynamically-allocated string. + * + * This uses the
    libwebsockets (see API documentation and installation + * instructions). To install on MacOS, we recommending using brew: + *
     brew install libwebsockets
    + * 
    This puts the compiled libraries in {@code /usr/local/lib}, and these libraries can be + * linked to providing the {@code -lwebsockets} compile option. + * + * A key limitation is that this should use the secure sockets API in libwebsockets to get SSL. + * + * @param hostport The host port number, which defaults to 8000. + * + * @author Edward A. Lee + */ +target C + +import WebSocketServer from "WebSocketServer.lf" + +reactor WebSocketServerString(hostport: int = 8000, initial_file: string = {= NULL =}) { + input in_dynamic: char* + input in_static: string + + output connected: bool + output received: char* + + state ws: web_socket_instance_t + + // Limit the number of clients to one. + server = new WebSocketServer(hostport=hostport, max_clients=1, initial_file=initial_file) + + reaction(startup) {= + self->ws.connected = false; + =} + + reaction(in_dynamic, in_static) -> server.send {= + if (self->ws.connected) { + web_socket_message_t* to_send = (web_socket_message_t*)malloc(sizeof(web_socket_message_t)); + char* message_copy; + if (in_dynamic->is_present) { + message_copy = (char*)malloc(strlen(in_dynamic->value)); + strcpy(message_copy, in_dynamic->value); + } else { + message_copy = (char*)malloc(strlen(in_static->value)); + strcpy(message_copy, in_static->value); + } + to_send->message = message_copy; + to_send->length = strlen(in_dynamic->value) + 1; + to_send->wsi = self->ws.wsi; + + lf_set(server.send, to_send); + } else { + // Web socket is not connected. + lf_print_warning("Web socket is not connected. Dropping message to send."); + } + =} + + reaction(server.connected) -> connected {= + if (server.connected->value.connected) { + self->ws = server.connected->value; + } + // Do not produce an output if the connected message is due to a denied connection. + if (self->ws.wsi == server.connected->value.wsi) { + lf_set(connected, server.connected->value.connected); + } + =} + + reaction(server.received) -> received {= + // NOTE: WebSocketServer ensures that the message is null terminated. + size_t len = strlen(server.received->value->message) + 1; + char* message = (char*)malloc(len * sizeof(char)); + strcpy(message, server.received->value->message); + lf_set_array(received, message, len); + =} +} diff --git a/examples/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf b/examples/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf index 1af7c3e3..fe7969c3 100644 --- a/examples/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf +++ b/examples/C/src/modal_models/FurutaPendulum/FurutaPendulum.lf @@ -16,7 +16,7 @@ target C { timeout: 3 secs, fast: true, - flags: "-lm", + cmake-include: "furuta.cmake", build: "./build_run_plot.sh FurutaPendulum" } diff --git a/examples/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf b/examples/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf index 873cb013..ad5ee330 100644 --- a/examples/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf +++ b/examples/C/src/modal_models/FurutaPendulum/FurutaPendulumDisturbance.lf @@ -8,7 +8,6 @@ target C { timeout: 5 secs, fast: true, - flags: "-lm", build: "./build_run_plot.sh FurutaPendulumDisturbance" } diff --git a/examples/C/src/modal_models/FurutaPendulum/build_run_plot.sh b/examples/C/src/modal_models/FurutaPendulum/build_run_plot.sh index 1e8dc089..2f9325e1 100755 --- a/examples/C/src/modal_models/FurutaPendulum/build_run_plot.sh +++ b/examples/C/src/modal_models/FurutaPendulum/build_run_plot.sh @@ -20,11 +20,11 @@ fi # Build the generated code. cd ${LF_SOURCE_GEN_DIRECTORY} -cmake -DLF_REACTION_GRAPH_BREADTH=3 -DLF_THREADED=1 -DNUMBER_OF_WORKERS=0 -DSCHEDULER=NP -DMODAL_REACTORS=TRUE . -cmake --build . +cmake -Bbuild +make -C build # Move the executable to the bin directory. -mv $1 ${LF_BIN_DIRECTORY} +mv build/$1 ${LF_BIN_DIRECTORY} # Move back to source directory to run program cd ${LF_SOURCE_DIRECTORY} diff --git a/examples/C/src/modal_models/FurutaPendulum/furuta.cmake b/examples/C/src/modal_models/FurutaPendulum/furuta.cmake new file mode 100644 index 00000000..430831ec --- /dev/null +++ b/examples/C/src/modal_models/FurutaPendulum/furuta.cmake @@ -0,0 +1 @@ +target_link_libraries(${LF_MAIN_TARGET} PRIVATE m) \ No newline at end of file diff --git a/examples/C/src/mqtt/MQTTDistributed.lf b/examples/C/src/mqtt/MQTTDistributed.lf index 6152b317..bdfb90b4 100644 --- a/examples/C/src/mqtt/MQTTDistributed.lf +++ b/examples/C/src/mqtt/MQTTDistributed.lf @@ -12,8 +12,8 @@ * The code generator produces three programs, bin/MQTTDistributed_RTI, bin/MQTTDistributed_source, * and bin/MQTTDistributed_destination, plus a script bin/MQTTDistributed that runs all three. * - * Since the source and destination are running in the same executable, there is no clock - * synchronization error. + * If the source and destination are running in the same machine, there is no clock synchronization + * error. * * See README.md for prerequisites and further information. * @@ -21,7 +21,6 @@ * @author Edward A. Lee */ target C { - cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 10 secs, coordination: centralized } diff --git a/examples/C/src/mqtt/MQTTDistributedActivity.lf b/examples/C/src/mqtt/MQTTDistributedActivity.lf index 7db8d95e..1678a9fe 100644 --- a/examples/C/src/mqtt/MQTTDistributedActivity.lf +++ b/examples/C/src/mqtt/MQTTDistributedActivity.lf @@ -11,7 +11,6 @@ * @author Edward A. Lee */ target C { - cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 10 secs, coordination: centralized } diff --git a/examples/C/src/mqtt/MQTTLegacy.lf b/examples/C/src/mqtt/MQTTLegacy.lf index 0065ae4f..ef384007 100644 --- a/examples/C/src/mqtt/MQTTLegacy.lf +++ b/examples/C/src/mqtt/MQTTLegacy.lf @@ -13,18 +13,13 @@ * * This is a federated program, the publisher and subscriber run in separate programs. This would * work pretty much the same way, however, as an unfederated program. To run as an unfederated - * program, add to cmake-include the following file: - * - * "include/net_utils.cmake" - * - * and change the `federated` keyword to `main`. + * program, just change the `federated` keyword to `main`. * * See README.md for prerequisites and further information. * * @author Edward A. Lee */ target C { - cmake-include: ["include/paho-extension.cmake", "include/mosquitto-extension.cmake"], timeout: 1 min, coordination: centralized } @@ -49,7 +44,7 @@ reactor Subscriber { sub.message -> dsp.message } -federated reactor { +main reactor { source = new Publisher() destination = new Subscriber() } diff --git a/examples/C/src/mqtt/MQTTLogical.lf b/examples/C/src/mqtt/MQTTLogical.lf index 3a6afb56..fdffb4a9 100644 --- a/examples/C/src/mqtt/MQTTLogical.lf +++ b/examples/C/src/mqtt/MQTTLogical.lf @@ -13,10 +13,6 @@ * @author Edward A. Lee */ target C { - cmake-include: [ - "include/paho-extension.cmake", // For #include "MQTTClient.h" - // For encode_int64() - "include/net_utils.cmake"], timeout: 10 secs } diff --git a/examples/C/src/mqtt/MQTTPhysical.lf b/examples/C/src/mqtt/MQTTPhysical.lf index 9094de08..70c881eb 100644 --- a/examples/C/src/mqtt/MQTTPhysical.lf +++ b/examples/C/src/mqtt/MQTTPhysical.lf @@ -12,10 +12,6 @@ * @author Edward A. Lee */ target C { - cmake-include: [ - "include/paho-extension.cmake", // For #include "MQTTClient.h" - // For encode_int64() and extract_int64() - "include/net_utils.cmake"], timeout: 10 secs } diff --git a/examples/C/src/mqtt/README.md b/examples/C/src/mqtt/README.md index 6506f72c..8eb3f409 100644 --- a/examples/C/src/mqtt/README.md +++ b/examples/C/src/mqtt/README.md @@ -25,8 +25,23 @@ The following examples illustrate more advanced features, particularly the limit ## Prerequisites: -To get this example to compile, you will need to install the [Eclipse Paho MQTT C client library](https://github.com/eclipse/paho.mqtt.c), which requires that you first install -[openSSL](https://github.com/openssl/openssl.git) (see [https://www.openssl.org](https://www.openssl.org). To run the compiled code, you need an MQTT broker to be running. For example, the [Mosquitto Eclipse project](https://mosquitto.org/download/) provides one. On a Mac, you can use homebrew to install the Mosquitto broker: +To get this example to compile, you will need to install + +1. [openSSL](https://github.com/openssl/openssl.git). See [https://www.openssl.org](https://www.openssl.org). +2. The [Eclipse Paho MQTT C client library](https://github.com/eclipse/paho.mqtt.c). E.g., the following might work: + +```shell + git clone git@github.com:eclipse/paho.mqtt.c.git + mkdir /tmp/build.paho ; cd /tmp/build.paho + cmake -DPAHO_WITH_SSL=TRUE -DPAHO_BUILD_DOCUMENTATION=TRUE \ + -DPAHO_BUILD_SAMPLES=TRUE ~/paho.mqtt.c + sudo make install + export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH +``` + +The last line could be put in your `~/.bash_profile` file so that you don't have to type each time you run the LF program in a new shell. + +To run the compiled code, you need an MQTT broker to be running. For example, the [Mosquitto Eclipse project](https://mosquitto.org/download/) provides one. On a Mac, you can use homebrew to install the Mosquitto broker: brew install mosquitto @@ -44,6 +59,12 @@ To start the broker and test it, do this: > mosquitto_pub -t 'test/topic' -m 'Hello World' +If you want to start the broker always upon login, you can do this: + +``` +brew services start mosquitto +``` + ## Implementation The [`MQTTPublisher`](https://github.com/lf-lang/examples-lingua-franca/blob/main/C/src/MQTT/lib/MQTTPublisher.lf) and [`MQTTSubscriber`](https://github.com/lf-lang/examples-lingua-franca/blob/main/C/src/MQTT/lib/MQTTSubscriber.lf) reactor use the [Paho MQTT Client Library](https://github.com/eclipse/paho.mqtt.c) (see the [documentation](https://www.eclipse.org/paho/files/mqttdoc/MQTTClient/html/_m_q_t_t_client_8h.html)). diff --git a/examples/C/src/mqtt/include/net_utils.cmake b/examples/C/src/mqtt/include/net_utils.cmake index 448ea62e..52ca4fe6 100644 --- a/examples/C/src/mqtt/include/net_utils.cmake +++ b/examples/C/src/mqtt/include/net_utils.cmake @@ -1 +1 @@ -target_sources(${LF_MAIN_TARGET} PRIVATE "core/federated/net_util.c") +target_sources(${LF_MAIN_TARGET} PRIVATE "core/federated/network/net_util.c") diff --git a/examples/C/src/mqtt/lib/MQTTPublisher.lf b/examples/C/src/mqtt/lib/MQTTPublisher.lf index bc49b6cd..9d3ab882 100644 --- a/examples/C/src/mqtt/lib/MQTTPublisher.lf +++ b/examples/C/src/mqtt/lib/MQTTPublisher.lf @@ -25,18 +25,22 @@ * @author Ravi Akella * @author Edward A. Lee */ -target C +target C { + cmake-include: [ + "../include/paho-extension.cmake", + // For encode_int64() + "../include/net_utils.cmake"] +} preamble {= #ifndef MQTT_PUBLISHER_H #define MQTT_PUBLISHER_H #include "platform.h" // Defines lf_critical_section_enter(), etc. - #include "tag.h" // Defines lf_time_logical() #include // Defines memcpy #include "MQTTClient.h" - #include "core/federated/net_util.h" + #include "core/federated/network/net_util.h" // Struct type used to keep track of messages in flight between reactions. typedef struct inflight_t { diff --git a/examples/C/src/mqtt/lib/MQTTSubscriber.lf b/examples/C/src/mqtt/lib/MQTTSubscriber.lf index e5ae4133..b55d79ce 100644 --- a/examples/C/src/mqtt/lib/MQTTSubscriber.lf +++ b/examples/C/src/mqtt/lib/MQTTSubscriber.lf @@ -36,18 +36,22 @@ * @author Ravi Akella * @author Edward A. Lee */ -target C +target C { + cmake-include: [ + "../include/paho-extension.cmake", + // For extract_int64() + "../include/net_utils.cmake"] +} preamble {= #ifndef MQTT_SUBSCRIBER_H #define MQTT_SUBSCRIBER_H #include "platform.h" // Defines lf_critical_section_enter(), etc. - #include "tag.h" // Defines lf_time_logical() #include // Defines memcmp() #include "MQTTClient.h" - #include "core/federated/net_util.h" + #include "core/federated/network/net_util.h" // Fix the QoS to indicate that the message will be delivered reliably exactly once. #define QOS 2 diff --git a/examples/C/src/rhythm/PlayWaveform.lf b/examples/C/src/rhythm/PlayWaveform.lf index 6238c436..41067fd5 100644 --- a/examples/C/src/rhythm/PlayWaveform.lf +++ b/examples/C/src/rhythm/PlayWaveform.lf @@ -26,7 +26,8 @@ target C { "/lib/c/reactor-c/util/wave_file_reader.h", "/lib/c/reactor-c/util/audio_loop_mac.c", "/lib/c/reactor-c/util/audio_loop.h", - "/lib/c/reactor-c/util/audio_loop_linux.c"], + "/lib/c/reactor-c/util/audio_loop_linux.c", + "sounds"], cmake-include: [ "/lib/c/reactor-c/util/audio_loop.cmake", "/lib/c/reactor-c/util/wave_file_reader.cmake"] @@ -44,7 +45,7 @@ reactor PlayWaveform( // wav files giving the waveforms. // These have to also be included in the files target directive. #define NUM_WAVEFORMS 9 // Number of waveforms. - #define SOUNDS LF_PACKAGE_DIRECTORY LF_FILE_SEPARATOR "src" LF_FILE_SEPARATOR "rhythm" LF_FILE_SEPARATOR "sounds" LF_FILE_SEPARATOR + #define SOUNDS LF_TARGET_FILES_DIRECTORY "/sounds/" char* waveform_files[] = { SOUNDS "Bass-Drum-1.wav", SOUNDS "Hi-Bongo.wav", diff --git a/examples/C/src/shared-memory/README.md b/examples/C/src/shared-memory/README.md new file mode 100644 index 00000000..5b5a959e --- /dev/null +++ b/examples/C/src/shared-memory/README.md @@ -0,0 +1,15 @@ +# Shared Memory + +The POSIX Realtime Extension includes a mechanism for processes on a single machine to share memory. A writer opens a "file" using `shm_open` and then uses `mmap` to map a sequence of memory addresses to the contents of this in-memory file. The `mmap` function returns a pointer to this memory, which the writer can then use to store data. + +A reader needs only the file name to open the file using `shm_open`, which it can then also map to memory locations using `mmap` + +This example shows how you can safely use this mechanism to exchange large chunks of data between LF federates without serializing, streaming, and then deserializing the data. The Sender reactor creates a file name using the current logical time (to ensure uniqueness, assuming no use of microsteps). It populates the shared memory with data and then sends the filename to the Reader. The Reader will only receive the file name after the Sender has finished writing to it, so precedence constraints are satisfied. + + + + + + +
    SharedMemory + SharedMemory.lf: An illustration of how to use shared memory to exchange large chunks of data between federates.
    \ No newline at end of file diff --git a/examples/C/src/shared-memory/SharedMemory.lf b/examples/C/src/shared-memory/SharedMemory.lf new file mode 100644 index 00000000..0c186ca3 --- /dev/null +++ b/examples/C/src/shared-memory/SharedMemory.lf @@ -0,0 +1,78 @@ +/** + * The POSIX Realtime Extension includes a mechanism for processes on a single machine to share + * memory. A writer opens a "file" using `shm_open` and then uses `mmap` to map a sequence of memory + * addresses to the contents of this in-memory file. The `mmap` function returns a pointer to this + * memory, which the writer can then use to store data. + * + * A reader needs only the file name to open the file using `shm_open`, which it can then also map + * to memory locations using `mmap`. + * + * This example shows how you can safely use this mechanism to exchange large chunks of data between + * LF federates without serializing, streaming, and then deserializing the data. The Sender reactor + * creates a file name using the current logical time (to ensure uniqueness, assuming no use of + * microsteps). It populates the shared memory with data and then sends the filename to the Reader. + * The Reader will only receive the file name after the Sender has finished writing to it, so + * precedence constraints are satisfied. + * + * @author Edward A. Lee + */ +target C { + timeout: 0 s +} + +preamble {= + #include + #include + #include + #include + #define SIZE 4096 +=} + +reactor Sender { + // Do not use string data type because the string filename is dynamically allocated. + output out: char* + + reaction(startup) -> out {= + tag_t now = lf_tag(); + char *name; + // Create a file name based on current time. + if (asprintf(&name, "Sender_" PRINTF_TIME "_%d", now.time, now.microstep) < 0) { + lf_print_error_and_exit("Memory allocation error."); + } + lf_print("**** Writing to shared memory with filename: %s", name); + int fd = shm_open(name, O_CREAT | O_RDWR, 0666); + ftruncate(fd, SIZE); // Limit the size. + char* ptr = (char*)mmap(0, SIZE, PROT_WRITE, MAP_SHARED, fd, 0); + const char* message = "Hello World!"; + + // Write to the shared memory file. + char* offset = ptr; + while (offset < ptr + SIZE - strlen(message)) { + sprintf(offset, "%s", message); + offset += strlen(message); + } + // Send out the file name only, not the data it contains. + lf_set_array(out, name, strlen(name) + 1); + =} +} + +reactor Reader { + input in: char* + + reaction(in) {= + lf_print("**** Reading shared memory file %s", in->value); + int fd = shm_open(in->value, O_RDONLY, 0666); + + // Memory map the shared memory object. + char* ptr = (char*)mmap(0, SIZE, PROT_READ, MAP_SHARED, fd, 0); + + // Read the shared memory data. + lf_print("%s", ptr); + =} +} + +federated reactor { + s = new Sender() + r = new Reader() + s.out -> r.in +} diff --git a/examples/C/src/shared-memory/img/SharedMemory.png b/examples/C/src/shared-memory/img/SharedMemory.png new file mode 100644 index 00000000..b607ca49 Binary files /dev/null and b/examples/C/src/shared-memory/img/SharedMemory.png differ diff --git a/examples/C/src/watchdog/README.md b/examples/C/src/watchdog/README.md new file mode 100644 index 00000000..c788c153 --- /dev/null +++ b/examples/C/src/watchdog/README.md @@ -0,0 +1,27 @@ +# Watchdog -- Reacting to Absent Inputs + +The LF deadline handlers react to inputs that are late. +What if you want to react the fact that an input has _not_ arrived by a certain time? +The deadline construct, by itself, will not do this. +It does not react until an input actually arrives. + +The watchdog construct provides a mechanism for this. +A watchdog is started using `lf_watchdog_start` at some logical time _t_. +It will expire at _physical_ time _t_ + _W_, where _W_ is the watchdog delay, +unless it gets restarted with `lf_watchdog_start` or stopped using `lf_watchdog_stop` +before that physical time elapses. + +When a watchdog expires, two things happen. First, the watchdog handler is invoked. +Second, an event identified by the name of the watchdog is scheduled at the earliest +available tag (typically the current tag plus one microstep). +The watchdog handler is invoked asynchronously, and therefore has limited access +to the reactor's infrastructure, such as inputs and outputs. +However, the scheduled watchdog event can trigger an ordinary reaction +which has full access to the state variables and inputs and outputs of the reactor. + + + + + +
    Watchdog + Watchdog.lf: Illustration of the use of the watchdogs in LF.
    \ No newline at end of file diff --git a/examples/C/src/watchdog/Watchdog.lf b/examples/C/src/watchdog/Watchdog.lf new file mode 100644 index 00000000..8d0db5ef --- /dev/null +++ b/examples/C/src/watchdog/Watchdog.lf @@ -0,0 +1,114 @@ +/** + * @brief Demonstration of watchdogs in LF. + * + * This program has a periodic source that triggers events every 500 ms, but where every fourth + * event is delayed before being delivered to the downstream Watcher. This delay models either + * communication or computation time. + * + * The Watcher has a deadline of 200 ms. It wants to see events within 200 ms of their logical time, + * but, because of the delay every fourth event, the deadline will be violated. The deadline + * violation handler is invoked in this case, but it is only invoked after the event arrives, which + * is late. What if we need to react to the deadline violation sooner, e.g. in order to drive an + * actuator? + * + * To handle this, the Watcher includes watchdog named "dog" that triggers if any input event is + * more than 250 ms late. + * + * @author Benjamin Asch + * @author Edward A. Lee + */ +target C { + timeout: 5 s +} + +reactor SometimesSlowSource { + output out: int + timer t(500 ms, 500 ms) // Offset ameliorates startup time. + state count: int = 1 + + reaction(t) -> out {= + if (self->count % 4 == 0) { + // Be slow. + lf_sleep(MSEC(300)); + } + lf_set(out, self->count++); + =} +} + +reactor Watcher { + input in: int + output out: int + state count: int = 1 + logical action a(500 ms) + + watchdog dog(750 ms) {= + instant_t p = lf_time_physical_elapsed(); + lf_print("******** Watchdog timed out at elapsed physical time: " PRINTF_TIME, p); + =} + + reaction(in) -> dog, out {= + // Reset the watchdog. The next event is expected in 500 ms. + // This watchdog will trigger if the event does not arrive within 750 ms from now, + // or is more than 250 ms late. + lf_watchdog_start(dog, 0); + lf_print("Watchdog started at physical time " PRINTF_TIME, lf_time_physical_elapsed()); + lf_print("Will expire at " PRINTF_TIME, lf_time_logical_elapsed() + MSEC(750)); + lf_set(out, in->value); + self->count++; + =} deadline(200 ms) {= + // This input is more than 200 ms late. It must have been from the slow cycles. + lf_print("Watcher received late input %d. Ignoring it.", in->value); + =} + + reaction(dog) -> a {= + // Note that this reaction will trigger at the earliest available tag, which + // will be one microstep later than the most recent input or watchdog violation. + lf_print("******** Watchdog triggered. Scheduling output for next logical time."); + // Use a logical action so that the logical time of the output aligns. + lf_schedule(a, 0); + =} + + reaction(a) -> out, dog {= + lf_print("******** Backup output being produced."); + lf_set(out, self->count++); + // Start another watchdog cycle in case the very next input is also delayed. + lf_watchdog_start(dog, 0); + lf_print("Watchdog started at physical time " PRINTF_TIME, lf_time_physical_elapsed()); + lf_print("Will expire at " PRINTF_TIME, lf_time_logical_elapsed() + MSEC(750)); + =} + + reaction(shutdown) -> dog {= + lf_watchdog_stop(dog); + =} +} + +reactor Checker { + input in: int + state count: int = 1 + + reaction(in) {= + lf_print("Checker received %d at logical time " PRINTF_TIME + " and physical time " PRINTF_TIME, in->value, + lf_time_logical_elapsed(), + lf_time_physical_elapsed()); + if (in->value != self->count) { + lf_print_error_and_exit("Expected %d", self->count); + } + self->count++; + =} + + reaction(shutdown) {= + if (self->count < 10) { + lf_print_error_and_exit("Received %d inputs. Expected at least 10.", self->count); + } + =} +} + +federated reactor { + s = new SometimesSlowSource() + w = new Watcher() + c = new Checker() + + s.out -> w.in + w.out -> c.in +} diff --git a/examples/C/src/watchdog/img/Watchdog.png b/examples/C/src/watchdog/img/Watchdog.png new file mode 100644 index 00000000..3bb720f5 Binary files /dev/null and b/examples/C/src/watchdog/img/Watchdog.png differ diff --git a/examples/CCpp/ROS/src/ROSBuiltInSerialization.lf b/examples/CCpp/ROS/src/ROSBuiltInSerialization.lf index cd234972..9f33f6fe 100644 --- a/examples/CCpp/ROS/src/ROSBuiltInSerialization.lf +++ b/examples/CCpp/ROS/src/ROSBuiltInSerialization.lf @@ -19,7 +19,6 @@ * @author Soroush Bateni */ target CCpp { - cmake: true, // Only CMake is supported cmake-include: "include/CMakeListsExtension.txt" } diff --git a/examples/Cpp/AlarmClock/src/AlarmClock.lf b/examples/Cpp/AlarmClock/src/AlarmClock.lf index 27992705..6ffe3051 100644 --- a/examples/Cpp/AlarmClock/src/AlarmClock.lf +++ b/examples/Cpp/AlarmClock/src/AlarmClock.lf @@ -1,29 +1,26 @@ /** - * This is a minimal example of an alarmclock implemeted using the features lingua franca supplies. + * This is a minimal example of an alarm clock implemeted using the features Lingua Franca supplies. * * This is just an extract and simplification from the main project which you can find here: * https://github.com/revol-xut/lf-alarm-clock * * This file contains the networking implementation it is really just an simple socket application - * which parses simple http headers and respondes in text/plain + * which parses simple HTTP headers and responds in text/plain * * @author Tassilo Tanneberer */ target Cpp { - cmake-include: "AlarmClock.cmake", - keepalive: true + cmake-include: "AlarmClock.cmake" } import Network from "./Network.lf" import Clock from "./Clock.lf" -// import Network.lf; -// import Clock.lf; main reactor AlarmClock { clock = new Clock() network = new Network() - network.event -> clock.event // additon of a new event + network.event -> clock.event // addition of a new event network.delete_index -> clock.cancel_by_index clock.event_dump -> network.updated_events diff --git a/examples/Cpp/AlarmClock/src/Clock.lf b/examples/Cpp/AlarmClock/src/Clock.lf index 7eef961d..61a71d5d 100644 --- a/examples/Cpp/AlarmClock/src/Clock.lf +++ b/examples/Cpp/AlarmClock/src/Clock.lf @@ -7,8 +7,7 @@ * Author: Tassilo Tanneberer */ target Cpp { - cmake-include: "AlarmClock.cmake", - keepalive: true + cmake-include: "AlarmClock.cmake" } public preamble {= diff --git a/examples/Cpp/AlarmClock/src/Network.lf b/examples/Cpp/AlarmClock/src/Network.lf index 2455d30d..58bef152 100644 --- a/examples/Cpp/AlarmClock/src/Network.lf +++ b/examples/Cpp/AlarmClock/src/Network.lf @@ -1,17 +1,19 @@ /** - * This is a minimal example of an alarmclock implemeted using the features lingua franca supplies. + * This is a minimal example of an alarm clock implemeted using the features Lingua Franca supplies. * * This is just an extract and simplification from the main project which you can find here: * https://github.com/revol-xut/lf-alarm-clock * * This file contains the networking implementation it is really just an simple socket application - * which parses simple http headers and respondes in text/plain + * which parses simple HTTP headers and responds in text/plain. + * + * This requires [installing Crow](https://crowcpp.org/master/getting_started/setup), which provides + * the HTTP server implementation. * * @author Tassilo Tanneberer */ target Cpp { - cmake-include: "AlarmClock.cmake", - keepalive: true + cmake-include: "AlarmClock.cmake" } public preamble {= diff --git a/examples/Cpp/AlarmClock/src/README.md b/examples/Cpp/AlarmClock/src/README.md index 914f796d..cb8a56e0 100644 --- a/examples/Cpp/AlarmClock/src/README.md +++ b/examples/Cpp/AlarmClock/src/README.md @@ -1,63 +1,29 @@ Lingua Franca Alarm Clock ---------------------------- -**Contact:** +**Author:** -**Main Repository:** [](https://github.com/revol-xut/lf-alarm-clock) - -A small and tiny alarmclock which is written using the scheduling and time features from lingua franca. - -## What you will learn - -- sharing state between reactors -- stopping scheduled events - -## Project +A small alarm clock which is written using the scheduling and time features from Lingua Franca. +This requires [installing Crow](https://crowcpp.org/master/getting_started/setup), which provides the HTTP server implementation. ![Programm Structure](./images/entire_program.png) - -## Building - -**Dependencies:** jdk11, boost, mpg321, Crow - - -```bash - $ lfc ./AlarmClock.lf -``` - -**Building with nix** - -This cross compiles for aarch64. -``` - nix build .#packages.aarch64-linux.lf-alarm-clock -``` - ## Installation By default the AlarmClock expects the sound files to be placed in `~/music/AlarmClock/` you can change this path by editing the `shared_header.cpp` file. Furthermore is it possible to configure paths to other binaries in this file e.g. kill, mpg321 -commands. -### Installing Crow from source - -On most distros, Crow needs to be build and installed from source: +This program requires that you first [install Crow](https://crowcpp.org/master/getting_started/setup). +If you have installed Crow in a location where CMake does not automatically find it, then you can manually specify the location when compiling the LF program as follows: -```bash - $ git clone git@github.com:CrowCpp/Crow.git - $ mkdir Crow/build - $ cd Crow/build - $ cmake -DCMAKE_INSTALL_PREFIX= - $ make install -``` -Note that you can adjust the preferred install location by replacing ``. - -To build the alarm clock using this manually installed version of Crow, simply run: ```bash $ CMAKE_PREFIX_PATH= lfc ./AlarmClock.lf ``` -## Endpoints & Usage +## Usage + +Running the program starts a web server on localhost at port 8680. The commands it understands are: ### /list **GET** Returns a list of upcoming events. @@ -90,7 +56,7 @@ Stops the currently playing alarm sound. $ curl http://0.0.0.0:8680/stop ``` -### /add_event_timestamp **POST** +### /add\_event\_timestamp **POST** Will schedule your alarmclock for the given timestamp Request: @@ -115,7 +81,7 @@ $ curl http://0.0.0.0:8680/add_event_timestamp -X POST -H "Content-Type: text/js Schedules event for given timestamp. -### /add_event_relative **POST** +### /add\_event\_relative **POST** Will schedule a event relative to the current time. Request diff --git a/examples/Cpp/CarBrake/src/README.md b/examples/Cpp/CarBrake/src/README.md index 2eedfa6d..b38e1f25 100644 --- a/examples/Cpp/CarBrake/src/README.md +++ b/examples/Cpp/CarBrake/src/README.md @@ -14,7 +14,7 @@ The second version forsakes consistency altogether using a Lingua Franca physica ## Variant that fixes this -The CarBrake2.lf variant, if made federated, decouples the vision system from the handling of brake pedal actions +The CarBrake2.lf variant decouples the vision system from the handling of brake pedal actions in a way that makes it impossible for the vision system to have any effect on the ability of the other component to make deadlines. The price for this decoupling is added nondeterminacy because the physical connection reassigns time stamps based on the current physical clock. @@ -24,4 +24,8 @@ reassigns time stamps based on the current physical clock. CarBrake CarBrake.lf: Sketch of an ADAS implementation that emphasizes strong consistency at the expense of availability and therefore will miss deadlines when the brake pedal is pushed while image analysis is being performed. + + CarBrake2 + CarBrake.lf: Variant that decouples the vision system from the handling of the brake pedal using a physical connection. + \ No newline at end of file diff --git a/examples/Cpp/CarBrake/src/img/CarBrake.png b/examples/Cpp/CarBrake/src/img/CarBrake.png new file mode 100644 index 00000000..08286dae Binary files /dev/null and b/examples/Cpp/CarBrake/src/img/CarBrake.png differ diff --git a/examples/Cpp/CarBrake/src/img/CarBrake2.png b/examples/Cpp/CarBrake/src/img/CarBrake2.png new file mode 100644 index 00000000..d78cd012 Binary files /dev/null and b/examples/Cpp/CarBrake/src/img/CarBrake2.png differ diff --git a/examples/Cpp/Patterns/README.md b/examples/Cpp/Patterns/README.md new file mode 100644 index 00000000..386d170d --- /dev/null +++ b/examples/Cpp/Patterns/README.md @@ -0,0 +1,6 @@ +# C++ Patterns +A few common design patterns: + +* [Fully Connected Broadcast](src/FullyConnected_00_Broadcast.lf) +* [Fully Connected Addressable](src/FullyConnected_00_Addressable.lf) +* [MatrixConnectedRowsAndColumns](src/MatrixConnectedRowsAndColumns.lf) diff --git a/examples/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf b/examples/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf index 796b116f..bfb67823 100644 --- a/examples/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf +++ b/examples/Cpp/Patterns/src/MatrixConnectedRowsAndColumns.lf @@ -1,6 +1,6 @@ // This pattern creates a matrix of nodes, where each of the nodes can send // messages to all other nodes in the same row or in the same column. Since -// banks in LF are one dimensional, we use hierachy to implement the second +// banks in LF are one dimensional, we use hierarchy to implement the second // dimension. Nodes are organized in Rows which are grouped to form the matrix. target Cpp diff --git a/examples/Cpp/README.md b/examples/Cpp/README.md new file mode 100644 index 00000000..df779532 --- /dev/null +++ b/examples/Cpp/README.md @@ -0,0 +1,7 @@ +# C++ Examples +* [Alarm Clock](AlarmClock/src/README.md) +* [Car Brake](CarBrake/src/README.md) +* [Patterns](Patterns/src/README.md) +* [Reflex Game](ReflexGame/src/ReflexGame.lf) +* [Add](RequestResponse/src/Add.lf) and [AddWithContext](RequestResponse/src/AddWithContext.lf) request-response patterns +* [ROS2](ROS2/src/README.md) diff --git a/examples/Cpp/ROS2/src/MinimalSubscriber.lf b/examples/Cpp/ROS2/src/MinimalSubscriber.lf index 5cd82f4e..444c10c8 100644 --- a/examples/Cpp/ROS2/src/MinimalSubscriber.lf +++ b/examples/Cpp/ROS2/src/MinimalSubscriber.lf @@ -8,7 +8,6 @@ */ target Cpp { ros2: true, - keepalive: true, ros2-dependencies: ["std_msgs"] } diff --git a/examples/Cpp/ROS2/src/README.md b/examples/Cpp/ROS2/src/README.md index 1ed55731..b4fc3833 100644 --- a/examples/Cpp/ROS2/src/README.md +++ b/examples/Cpp/ROS2/src/README.md @@ -1,5 +1,5 @@ -This is an LF reimplementation of the ROS 2 minimal publisher and sunscriber +This is an LF reimplementation of the ROS 2 minimal publisher and subscriber [example](https://docs.ros.org/en/galactic/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html). -It consists of two LF files, MinimalPublisher and MinimalSubscriber, each +It consists of two LF files, [MinimalPublisher.lf](MinimalPublisher.lf) and [MinimalSubscriber.lf](MinimalSubscriber.lf), each implementing the corresponding nodes from the original example. diff --git a/examples/Python/src/CARLA/README.md b/examples/Python/src/CARLA/README.md new file mode 100644 index 00000000..b1b868d4 --- /dev/null +++ b/examples/Python/src/CARLA/README.md @@ -0,0 +1,74 @@ +# CARLA + +This guide provides detailed instructions for running the CARLA simulator with LF. Ensure the following prerequisites are met before proceeding with the installation: + +- **System requirements.** CARLA is built for Windows and Linux systems. +- **An adequate GPU.** CARLA aims for realistic simulations, so the server needs at least a 6 GB GPU although we would recommend 8 GB. A dedicated GPU is highly recommended for machine learning. +- **Disk space.** CARLA will use about 20 GB of space. +- **Two TCP ports and good internet connection.** 2000 and 2001 by default. Make sure that these ports are not blocked by firewalls or any other applications. +- **Other requirements.** CARLA requires some Python dependencies. Install the dependencies with the following command: + +```bash +pip3 install --user pygame numpy carla +``` + +### **Downloading CARLA** + +Download CARLA version 0.9.15 from [the official repository](https://github.com/carla-simulator/carla/releases/tag/0.9.15/). Extract the release file, which contains a precompiled version of the simulator. + +### **Running the CARLA Server** + +1. **On Windows:** Navigate to the extracted CARLA folder and double-click the `CarlaUE4.exe` file to start the server. A window will appear indicating the server is active. +2. **On Linux:** In the terminal, navigate to the CARLA folder and run `./CarlaUE4.sh` to initiate the server. + +Note: Please restart the CARLA Server before running each of the examples below. + +### **Compiling LF** + +Compile the `carla_sync.lf` and `carla_async.lf` using the `lfc` command. Make sure that `carla_client.py` is in the include folder. + +### Synchronous Example - Manual Control + +Run the generated script `/.carla_sync` which connects to the CARLA server in synchronous mode and initiates a driving simulator in pygame. Use ARROWS or WASD keys for control. Image and IMU data will be displayed on the console. + +drawing + +### Asynchronous Example - Driving in Circles + +Run the generated script `/.carla_async` which connects to the CARLA server in asynchronous mode and initiates a driving simulator in pygame. The vehicle will drive in circles. Image and IMU data will be displayed on the console. + +drawing + +### Possible Configurations of CARLA Server + +The configuration of time-step and synchrony, leads for different settings. Here is a brief summary on the possibilities. + +| | Fixed time-step | Variable time-step | +| ----------------- | ---------------------------------------------------------------------- | ---------------------------------- | +| Synchronous mode | Client is in total control over the simulation and its information. | Risk of non reliable simulations. | +| Asynchronous mode | Good time references for information. Server runs as fast as possible. | Non easily repeatable simulations. | + +- **Synchronous mode + variable time-step.** This is almost for sure a non-desirable state. Physics cannot run properly when the time-step is bigger than 0.1s, and if the server has to wait for the client to compute the steps, this is likely to happen. Simulation time and physics will not be in synchrony. The simulation will not be reliable. +- **Asynchronous mode + variable time-step.** This is the default CARLA state. Client and server are asynchronous. The simulation time flows according to the real time. Reenacting the simulation needs to take into account float-arithmetic error, and possible differences in time steps between servers. +- **Asynchronous mode + fixed time-step.** The server will run as fast as possible. The information retrieved will be easily related with an exact moment in the simulation. This configuration makes possible to simulate long periods of time in much less real time, if the server is fast enough. +- **Synchronous mode + fixed time-step.** The client will rule the simulation. The time step will be fixed. The server will not compute the following step until the client sends a tick. This is the best mode when synchrony and precision is relevant, especially when dealing with slow clients or different elements retrieving information. + +**Fixed time-step** + +Fixed delta seconds can be set in the world settings. + +```python +settings = world.get_settings() +settings.fixed_delta_seconds = 0.05 +world.apply_settings(settings) +``` + +**Variable time-step** + +The default mode in CARLA. The simulation time that goes by between steps will be the time that the server takes to compute these. + +```python +settings = world.get_settings() +settings.fixed_delta_seconds = None # Set a variable time-step +world.apply_settings(settings) +``` diff --git a/examples/Python/src/CARLA/carla_client.py b/examples/Python/src/CARLA/carla_client.py new file mode 100644 index 00000000..d1e7b6be --- /dev/null +++ b/examples/Python/src/CARLA/carla_client.py @@ -0,0 +1,229 @@ +import carla +import weakref +import random +import pygame +from pygame.locals import K_ESCAPE +from pygame.locals import K_SPACE +from pygame.locals import K_a +from pygame.locals import K_d +from pygame.locals import K_s +from pygame.locals import K_w +import numpy as np +from abc import ABC, abstractmethod + +VIEW_WIDTH = 1920//2 +VIEW_HEIGHT = 1080//2 +VIEW_FOV = 90 +BB_COLOR = (248, 64, 24) + + +def process_image_data(image): + """ + Processes the raw image data from the camera sensor. + """ + array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) + array = np.reshape(array, (image.height, image.width, 4)) + array = array[:, :, :3] + array = array[:, :, ::-1] + return array + + +class BasicClient(ABC): + """ + Basic implementation of a synchronous client. + """ + @abstractmethod + def game_start(self): + pass + + @abstractmethod + def control(self, car): + pass + + def __init__(self): + self.client = None + self.world = None + self.camera = None + self.car = None + + self.display = None + self.image = None + self.capture = True + + def camera_blueprint(self): + """ + Returns camera blueprint. + """ + camera_bp = self.world.get_blueprint_library().find("sensor.camera.rgb") + camera_bp.set_attribute("image_size_x", str(VIEW_WIDTH)) + camera_bp.set_attribute("image_size_y", str(VIEW_HEIGHT)) + camera_bp.set_attribute("fov", str(VIEW_FOV)) + return camera_bp + + def set_synchronous_mode(self, synchronous_mode): + """ + Sets synchronous mode. + """ + settings = self.world.get_settings() + settings.fixed_delta_seconds = 0.05 + settings.synchronous_mode = synchronous_mode + self.world.apply_settings(settings) + + def setup_car(self): + """ + Spawns actor-vehicle to be controlled. + + """ + car_bp = self.world.get_blueprint_library().filter("vehicle.*")[0] + location = random.choice(self.world.get_map().get_spawn_points()) + self.car = self.world.spawn_actor(car_bp, location) + + def setup_imu(self): + """ + Spawns actor-IMU sensor to be used to get IMU data. + """ + imu_bp = self.world.get_blueprint_library().find("sensor.other.imu") + imu_transform = carla.Transform(carla.Location(x=0.5, z=2.8)) + self.imu = self.world.spawn_actor( + imu_bp, imu_transform, attach_to=self.car) + weak_self = weakref.ref(self) + self.imu.listen(lambda data: weak_self().process_imu_data(data)) + + def setup_camera(self): + """ + Spawns actor-camera to be used to render view. + """ + camera_transform = carla.Transform(carla.Location( + x=-5.5, z=2.8), carla.Rotation(pitch=-15)) + self.camera = self.world.spawn_actor( + self.camera_blueprint(), camera_transform, attach_to=self.car) + weak_self = weakref.ref(self) + self.camera.listen( + lambda image: weak_self().set_image(weak_self, image)) + + calibration = np.identity(3) + calibration[0, 2] = VIEW_WIDTH / 2.0 + calibration[1, 2] = VIEW_HEIGHT / 2.0 + calibration[0, 0] = calibration[1, 1] = VIEW_WIDTH / \ + (2.0 * np.tan(VIEW_FOV * np.pi / 360.0)) + self.camera.calibration = calibration + + def process_imu_data(self, data): + """ + Processes and stores IMU data. + """ + self.imu_data = data + + @staticmethod + def set_image(weak_self, img): + self = weak_self() + if self.capture: + self.image = img + self.capture = False + + def render(self, display): + """ + Renders the image on the display. + """ + if self.image is not None: + array = process_image_data(self.image) + surface = pygame.surfarray.make_surface(array.swapaxes(0, 1)) + display.blit(surface, (0, 0)) + return array + + def game_step(self): + """ + Simulates one step in the game, processing inputs and rendering the image. + """ + self.world.tick() + self.capture = True + self.pygame_clock.tick_busy_loop(20) + image = self.render(self.display) + + pygame.display.flip() + pygame.event.pump() + + sensor_data = None + if not self.image or hasattr(self, "imu_data"): + sensor_data = [self.image, self.imu_data.accelerometer.x] + + self.control(self.car) + + return sensor_data + + +class SyncClient(BasicClient): + def control(self, car): + keys = pygame.key.get_pressed() + if keys[K_ESCAPE]: + return True + + control = car.get_control() + control.throttle = 0 + if keys[K_w]: + control.throttle = 1 + control.reverse = False + elif keys[K_s]: + control.throttle = 1 + control.reverse = True + if keys[K_a]: + control.steer = max(-1., min(control.steer - 0.05, 0)) + elif keys[K_d]: + control.steer = min(1., max(control.steer + 0.05, 0)) + else: + control.steer = 0 + control.hand_brake = keys[K_SPACE] + + car.apply_control(control) + return False + + def game_start(self): + """ + Initializes the game, setting up the client, car, camera, IMU, and display. + """ + pygame.init() + + self.client = carla.Client("localhost", 2000) + self.client.set_timeout(10.0) + self.world = self.client.get_world() + + self.setup_car() + self.setup_camera() + self.setup_imu() + + self.display = pygame.display.set_mode( + (VIEW_WIDTH, VIEW_HEIGHT), pygame.HWSURFACE | pygame.DOUBLEBUF) + self.pygame_clock = pygame.time.Clock() + + self.set_synchronous_mode(True) + vehicles = self.world.get_actors().filter("vehicle.*") + + +class AsyncClient(BasicClient): + def control(self, car): + control = car.get_control() + control.throttle = 1 + control.steer = 1 + car.apply_control(control) + return False + + def game_start(self): + """ + Initializes the game, setting up the client, car, camera, IMU, and display. + """ + pygame.init() + + self.client = carla.Client("172.23.112.1", 2000) + self.client.set_timeout(10.0) + self.world = self.client.get_world() + + self.setup_car() + self.setup_camera() + self.setup_imu() + + self.display = pygame.display.set_mode( + (VIEW_WIDTH, VIEW_HEIGHT), pygame.HWSURFACE | pygame.DOUBLEBUF) + self.pygame_clock = pygame.time.Clock() + + self.set_synchronous_mode(False) + vehicles = self.world.get_actors().filter("vehicle.*") diff --git a/examples/Python/src/CARLA/img/carla_circles.gif b/examples/Python/src/CARLA/img/carla_circles.gif new file mode 100644 index 00000000..076260ad Binary files /dev/null and b/examples/Python/src/CARLA/img/carla_circles.gif differ diff --git a/examples/Python/src/CARLA/img/carla_manual.png b/examples/Python/src/CARLA/img/carla_manual.png new file mode 100644 index 00000000..7016d84d Binary files /dev/null and b/examples/Python/src/CARLA/img/carla_manual.png differ diff --git a/examples/Python/src/CARLA/src/carla_async.lf b/examples/Python/src/CARLA/src/carla_async.lf new file mode 100644 index 00000000..6b2edca0 --- /dev/null +++ b/examples/Python/src/CARLA/src/carla_async.lf @@ -0,0 +1,71 @@ +target Python { + files: carla_client.py +} + +preamble {= + from carla_client import AsyncClient + from carla_client import process_image_data +=} + +reactor Carla { + input actions + output raw_image + output imu + state client + + reaction(startup) -> raw_image, imu {= + self.client = AsyncClient() + self.client.game_start() + sensor_data = self.client.game_step() + raw_image.set(sensor_data[0]) + imu.set(sensor_data[1]) + =} + + reaction(actions) -> raw_image, imu {= + sensor_data = self.client.game_step() + raw_image.set(sensor_data[0]) + imu.set(sensor_data[1]) + =} +} + +reactor Image { + input raw_image + output processed_image + + reaction(startup) {= =} + + reaction(raw_image) -> processed_image {= + if raw_image.value: + array = process_image_data(raw_image.value) + processed_image.set(array) + else: + processed_image.set(None) + =} +} + +reactor Fusion { + input imu + input processed_image + output actions + + reaction(startup) {= =} + + reaction(imu, processed_image) -> actions {= + if imu is not None: + print("IMU Data: ", imu.value) + if processed_image.value is not None: + print("Image Data: ", processed_image.value[0][0]) + actions.set(0) + =} +} + +main reactor { + carla = new Carla() + image = new Image() + fusion = new Fusion() + + carla.raw_image -> image.raw_image + carla.imu -> fusion.imu + image.processed_image -> fusion.processed_image + fusion.actions -> carla.actions after 50 ms +} diff --git a/examples/Python/src/CARLA/src/carla_sync.lf b/examples/Python/src/CARLA/src/carla_sync.lf new file mode 100644 index 00000000..87b11f65 --- /dev/null +++ b/examples/Python/src/CARLA/src/carla_sync.lf @@ -0,0 +1,71 @@ +target Python { + files: carla_client.py +} + +preamble {= + from carla_client import SyncClient + from carla_client import process_image_data +=} + +reactor Carla { + input actions + output raw_image + output imu + state client + + reaction(startup) -> raw_image, imu {= + self.client = SyncClient() + self.client.game_start() + sensor_data = self.client.game_step() + raw_image.set(sensor_data[0]) + imu.set(sensor_data[1]) + =} + + reaction(actions) -> raw_image, imu {= + sensor_data = self.client.game_step() + raw_image.set(sensor_data[0]) + imu.set(sensor_data[1]) + =} +} + +reactor Image { + input raw_image + output processed_image + + reaction(startup) {= =} + + reaction(raw_image) -> processed_image {= + if raw_image.value: + array = process_image_data(raw_image.value) + processed_image.set(array) + else: + processed_image.set(None) + =} +} + +reactor Fusion { + input imu + input processed_image + output actions + + reaction(startup) {= =} + + reaction(imu, processed_image) -> actions {= + if imu is not None: + print("IMU Data: ", imu.value) + if processed_image.value is not None: + print("Image Data: ", processed_image.value[0][0]) + actions.set(0) + =} +} + +main reactor { + carla = new Carla() + image = new Image() + fusion = new Fusion() + + carla.raw_image -> image.raw_image + carla.imu -> fusion.imu + image.processed_image -> fusion.processed_image + fusion.actions -> carla.actions after 0 +} diff --git a/examples/Python/src/YOLOv5/requirements.txt b/examples/Python/src/YOLOv5/requirements.txt index c7a23361..f21d6362 100644 --- a/examples/Python/src/YOLOv5/requirements.txt +++ b/examples/Python/src/YOLOv5/requirements.txt @@ -10,6 +10,7 @@ numpy>=1.18.5 Pillow>=7.1.2 PyYAML>=5.3.1 scipy>=1.4.1 +requests # Logging ------------------------------------- tensorboard>=2.4.1 diff --git a/examples/Python/src/acas/lib/XYPlotter.lf b/examples/Python/src/acas/lib/XYPlotter.lf index 4d44342d..01179493 100644 --- a/examples/Python/src/acas/lib/XYPlotter.lf +++ b/examples/Python/src/acas/lib/XYPlotter.lf @@ -30,15 +30,15 @@ reactor XYPlotter { reaction(shutdown) {= # make data - x1 = np.array(self.x1_list) - y1 = np.array(self.y1_list) - x2 = np.array(self.x2_list) - y2 = np.array(self.y2_list) + x1 = np.array(self.x1_list) + y1 = np.array(self.y1_list) + x2 = np.array(self.x2_list) + y2 = np.array(self.y2_list) - # plot - plt.plot(x1, y1, "b") - plt.plot(x2, y2, "r") + # plot + plt.plot(x1, y1, "b") + plt.plot(x2, y2, "r") - plt.show() + plt.show() =} } diff --git a/examples/README.md b/examples/README.md index eb4913c5..5a81456a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,2 +1,5 @@ # Examples -Our examples are organized by __target__. \ No newline at end of file +Our examples are organized by __target__ language: + +* [C examples](C/README.md) +* [C++ examples](Cpp/README.md) \ No newline at end of file diff --git a/experiments/C/src/Autoware/Autoware.lf b/experiments/C/src/Autoware/Autoware.lf index 9dfb9bc5..c510e8a2 100644 --- a/experiments/C/src/Autoware/Autoware.lf +++ b/experiments/C/src/Autoware/Autoware.lf @@ -1,39 +1,34 @@ /** - * This Lingua Franca (LF) program simulates an autonomous driving system inspired - * by Autoware's general graph structure and constraints. The system involves two - * sensors, a Camera and a LIDAR, generating periodic image and point-cloud data - * respectively. These data are processed by two object detection reactors, which - * simulate GPU computation using simulated delays and threading. The detected - * objects from both the image and LIDAR data are then merged in a DataFusion - * reactor. This fusion reactor is designed to handle the arrival of data from both - * inputs before proceeding, implementing a choke point that collapses multiple - * logical timelines into one. - * - * The fused data is passed to a Semantics reactor, which makes driving decisions - * based on the fused data, and the resultant actuation command is passed to an - * Actuator reactor. The Actuator reactor operates under a deadline, issuing a - * warning if this deadline is violated. Moreover, an alternative SafetyBrake path - * exists that can trigger the actuation based on LIDAR input directly. The whole - * system is designed to enforce a Logical Execution Time (LET) and meet end-to-end - * deadlines while taking into account the worst-case execution times of different - * components. This program offers a high-level simulation of real-time data - * processing and decision-making in autonomous driving systems. + * This Lingua Franca (LF) program simulates an autonomous driving system inspired by Autoware's + * general graph structure and constraints. The system involves two sensors, a Camera and a LIDAR, + * generating periodic image and point-cloud data respectively. These data are processed by two + * object detection reactors, which simulate GPU computation using simulated delays and threading. + * The detected objects from both the image and LIDAR data are then merged in a DataFusion reactor. + * This fusion reactor is designed to handle the arrival of data from both inputs before proceeding, + * implementing a choke point that collapses multiple logical timelines into one. + * + * The fused data is passed to a Semantics reactor, which makes driving decisions based on the fused + * data, and the resultant actuation command is passed to an Actuator reactor. The Actuator reactor + * operates under a deadline, issuing a warning if this deadline is violated. Moreover, an + * alternative SafetyBrake path exists that can trigger the actuation based on LIDAR input directly. + * The whole system is designed to enforce a Logical Execution Time (LET) and meet end-to-end + * deadlines while taking into account the worst-case execution times of different components. This + * program offers a high-level simulation of real-time data processing and decision-making in + * autonomous driving systems. */ - // A lingua franca program with Autoware's general graph structure and constraints // The tentative constraints are: // - Sensors: Camera(phase=3, period=33) and LIDAR(phase=10, period=10) // - LET=30ms or alternatively, the relative deadline is 30 // - Sensor Fusion causes merge (choke) points that collapse multiple logical timelines into one. - target C { keepalive: false -}; +} // Send a periodic image out -reactor Camera(offset:time(0), period:time(1 sec)) { - output image:string; - timer t(offset, period); +reactor Camera(offset: time = 0, period: time = 1 sec) { + output image: string + timer t(offset, period) reaction(t) -> image {= lf_set(image, "kitty"); @@ -41,9 +36,9 @@ reactor Camera(offset:time(0), period:time(1 sec)) { } // Send a periodic LIDAR pointcloud out -reactor LIDAR(offset:time(0), period:time(1 sec)) { - output pointcloud:string; - timer t(offset, period); +reactor LIDAR(offset: time = 0, period: time = 1 sec) { + output pointcloud: string + timer t(offset, period) reaction(t) -> pointcloud {= lf_set(pointcloud, "INTERSECTION"); @@ -52,31 +47,31 @@ reactor LIDAR(offset:time(0), period:time(1 sec)) { // Simulate object detection on GPU reactor ImageObjectDetection { - input image:string; - output object:string; + input image: string + output object: string + + state thread_id: pthread_t = 0 - - state thread_id:pthread_t(0); - preamble {= - #include - - void camera_callback(void* a) { - // cudaDeviceSynchronize() and copy back goes here. - lf_schedule(a, 0); - } - // Simulate time passing before a callback occurs instead of executing on GPU. - void* camera_take_time(void* a) { - struct timespec sleep_time = {(time_t) 0, (long)5000000}; // WCET for GPU is 5msec - struct timespec remaining_time; - nanosleep(&sleep_time, &remaining_time); - camera_callback(a); - return NULL; - } - pthread_t threadId; + #include + + void camera_callback(void* a) { + // cudaDeviceSynchronize() and copy back goes here. + lf_schedule(a, 0); + } + // Simulate time passing before a callback occurs instead of executing on GPU. + void* camera_take_time(void* a) { + struct timespec sleep_time = {(time_t) 0, (long)5000000}; // WCET for GPU is 5msec + struct timespec remaining_time; + nanosleep(&sleep_time, &remaining_time); + camera_callback(a); + return NULL; + } + pthread_t threadId; =} - physical action CUDA(100 msec); + physical action CUDA(100 msec) + reaction(image) -> CUDA {= // cudaMalloc(&y_state_d, SIZE); // cudaMemcpy(&y_state_d, y_state, SIZE, cudaMemcpyHosttoDevice); @@ -92,7 +87,7 @@ reactor ImageObjectDetection { reaction(CUDA) -> object {= // cudaMalloc(&y_out_d, SIZE); - + // cudaMemcpy(&y_out, results_d, SIZE, cudaMemcpyDevicetoHost); // cudaDeviceSynchronize(); @@ -105,72 +100,70 @@ reactor ImageObjectDetection { // Simulate LIDAR object detection on GPU reactor LIDARObjectDetection { - input pointcloud:string; - output object:string; + input pointcloud: string + output object: string + + state thread_id: pthread_t = 0 - - state thread_id:pthread_t(0); - preamble {= - #include - - void LIDAR_callback(void* a) { - // cudaDeviceSynchronize() and copy back goes here. - lf_schedule(a, 0); - } - // Simulate time passing before a callback occurs instead of executing on GPU. - void* LIDAR_take_time(void* a) { - struct timespec sleep_time = {(time_t) 0, (long)12000000}; // WCET for LIDAR GPU is 12 msec - struct timespec remaining_time; - nanosleep(&sleep_time, &remaining_time); - LIDAR_callback(a); - return NULL; - } - pthread_t threadId; - =} + #include - logical action CUDA(100 msec, 20 msec); - reaction(pointcloud) -> CUDA {= - // self->y_state = y_in; - // cudaMalloc(&y_state_d, SIZE); - // cudaMemcpy(&y_state_d, y_state, SIZE, cudaMemcpyHosttoDevice); - // kernel<<<1,1>>>(y_state_d); - //printf("LIDAR triggered at %llu\n", lf_time_physical()); + void LIDAR_callback(void* a) { + // cudaDeviceSynchronize() and copy back goes here. + lf_schedule(a, 0); + } + // Simulate time passing before a callback occurs instead of executing on GPU. + void* LIDAR_take_time(void* a) { + struct timespec sleep_time = {(time_t) 0, (long)12000000}; // WCET for LIDAR GPU is 12 msec + struct timespec remaining_time; + nanosleep(&sleep_time, &remaining_time); + LIDAR_callback(a); + return NULL; + } + pthread_t threadId; + =} - /* Busy wait for 2 msecs for now instead of calling CUDA kernels */ - interval_t sleep_time = MSEC(2); - instant_t start_time = lf_time_physical(); - while (lf_time_physical() < start_time + sleep_time) {}; + logical action CUDA(100 msec, 20 msec) - pthread_create(&self->thread_id, NULL, &LIDAR_take_time, CUDA); + reaction(pointcloud) -> CUDA {= + // self->y_state = y_in; + // cudaMalloc(&y_state_d, SIZE); + // cudaMemcpy(&y_state_d, y_state, SIZE, cudaMemcpyHosttoDevice); + // kernel<<<1,1>>>(y_state_d); + //printf("LIDAR triggered at %llu\n", lf_time_physical()); + + /* Busy wait for 2 msecs for now instead of calling CUDA kernels */ + interval_t sleep_time = MSEC(2); + instant_t start_time = lf_time_physical(); + while (lf_time_physical() < start_time + sleep_time) {}; + + pthread_create(&self->thread_id, NULL, &LIDAR_take_time, CUDA); =} reaction(CUDA) -> object {= - // cudaMalloc(&y_out_d, SIZE); - //printf("LIDAR triggered at %llu\n", lf_time_physical()); - // printf("LIDAR triggered at %llu\n", lf_time_logical()); - - // cudaMemcpy(&y_out, results_d, SIZE, cudaMemcpyDevicetoHost); + // cudaMalloc(&y_out_d, SIZE); + //printf("LIDAR triggered at %llu\n", lf_time_physical()); + // printf("LIDAR triggered at %llu\n", lf_time_logical()); - // cudaDeviceSynchronize(); + // cudaMemcpy(&y_out, results_d, SIZE, cudaMemcpyDevicetoHost); - lf_set(object, "Hey look there is a kitty!"); + // cudaDeviceSynchronize(); + + lf_set(object, "Hey look there is a kitty!"); =} } - // Fuse LIDAR and Camera detected objects -reactor DataFusion(threshold:time(20 msec)) { - input imageobject:string; - input LIDARobject:string; - output object:string; - logical action both_ports_are_present(0); - - state tmpImageobject:string(""); - state tmpImageobjectTag:time(0); - state tmpLIDARobject:string(""); - state tmpLIDARobjectTag:time(0); +reactor DataFusion(threshold: time = 20 msec) { + input imageobject: string + input LIDARobject: string + output object: string + logical action both_ports_are_present(0) + state tmpImageobject: string = "" + state tmpImageobjectTag: time = 0 + state tmpLIDARobject: string = "" + state tmpLIDARobjectTag: time = 0 // Handle two ports reaction(imageobject, LIDARobject) -> both_ports_are_present {= @@ -178,20 +171,20 @@ reactor DataFusion(threshold:time(20 msec)) { { self->tmpImageobject = imageobject->value; self->tmpImageobjectTag = lf_time_logical(); // Store the tag - + instant_t window = self->tmpLIDARobjectTag - lf_time_logical(); - + if(LIDARobject->is_present) { lf_schedule(both_ports_are_present, 0); } - + else if(window < (long)1000000) { lf_schedule(both_ports_are_present, 0); } else - { + { // instant_t elapsed = lf_time_physical() - lf_time_logical(); // How much time has passed - soft instant_t elapsed = self->threshold; // How much time has passed - hard instant_t remaining = (long)30000000 - elapsed; // How much time is left @@ -204,9 +197,9 @@ reactor DataFusion(threshold:time(20 msec)) { { self->tmpLIDARobject = LIDARobject->value; self->tmpLIDARobjectTag = lf_time_logical(); // Store the tag - - instant_t window = self->tmpImageobjectTag - lf_time_logical(); - + + instant_t window = self->tmpImageobjectTag - lf_time_logical(); + if( window < (long)1000000) { lf_schedule(both_ports_are_present, 0); @@ -215,23 +208,18 @@ reactor DataFusion(threshold:time(20 msec)) { { // instant_t elapsed = lf_time_physical() - lf_time_logical(); // How much time has passed - soft instant_t elapsed = self->threshold; // How much time has passed - hard - instant_t remaining = (long)30000000 - elapsed; // How much time is left + instant_t remaining = (long)30000000 - elapsed; // How much time is left instant_t schedule_in = remaining - (long)8000000; // Combined WCET of the remaining reactions is 8 msec lf_schedule(both_ports_are_present, schedule_in); - } + } // printf("Received LIDAR object at %llu physical and %llu logical.\n", lf_time_physical(), lf_time_logical()); } - - - =} deadline(threshold) {= printf("Deadline violation detected.\n"); =} // Fuse - reaction(both_ports_are_present) -> object - {= - + reaction(both_ports_are_present) -> object {= printf("Fusion scheduled at: ( %llu , %llu ).\n", lf_time_physical(), lf_time_logical()); //printf("Fusion: %llu\n", lf_time_physical()); /* Busy wait for 2 msecs for now instead of calling CUDA kernels */ @@ -244,19 +232,19 @@ reactor DataFusion(threshold:time(20 msec)) { // Make driving semantic decisions reactor Semantics { - input fusedObject:string; - output actuation:int; + input fusedObject: string + output actuation: int reaction(fusedObject) -> actuation {= - struct timespec sleep_time = {(time_t) 0, (long)6000000}; - struct timespec remaining_time; - nanosleep(&sleep_time, &remaining_time); - lf_set(actuation, 5); + struct timespec sleep_time = {(time_t) 0, (long)6000000}; + struct timespec remaining_time; + nanosleep(&sleep_time, &remaining_time); + lf_set(actuation, 5); =} } -reactor Actuator(threshold:time(33 msec)){ - input actuation:int; +reactor Actuator(threshold: time = 33 msec) { + input actuation: int reaction(actuation) {= // Do nothing; @@ -267,47 +255,45 @@ reactor Actuator(threshold:time(33 msec)){ =} } -/* An alternative path exists between the LIDAR and the actuator for the safety brake system */ +/** An alternative path exists between the LIDAR and the actuator for the safety brake system */ reactor SafetyBrake { - input LIDARPointCloud:string; - output actuation:int; + input LIDARPointCloud: string + output actuation: int reaction(LIDARPointCloud) -> actuation {= lf_set(actuation, 5); =} } - // End-to-end daedline should be 33ms // LET must be enforced with an output period of 33ms // GPU is used // Sub-deadlines (i.e., reaction deadlines) must not be violated - main reactor Autoware { - c = new Camera(offset = 3 msec, period = 33 msec); // Camera has a phase (startup time) of 3 msec and a period of 33 msec - l = new LIDAR(offset = 10 msec, period = 10 msec); // Lidar has a phase (spooling up time) of 10 msec and a period of 10 msec - - iobjectdetection = new ImageObjectDetection(); - c.image -> iobjectdetection.image; + // Camera has a phase (startup time) of 3 msec and a period of 33 msec + c = new Camera(offset = 3 msec, period = 33 msec) + // Lidar has a phase (spooling up time) of 10 msec and a period of 10 msec + l = new LIDAR(offset = 10 msec, period = 10 msec) - lobjectdetection = new LIDARObjectDetection(); - l.pointcloud -> lobjectdetection.pointcloud; + iobjectdetection = new ImageObjectDetection() + c.image -> iobjectdetection.image - // Choke point - fuse = new DataFusion(); - iobjectdetection.object -> fuse.imageobject; - lobjectdetection.object -> fuse.LIDARobject; + lobjectdetection = new LIDARObjectDetection() + l.pointcloud -> lobjectdetection.pointcloud + // Choke point + fuse = new DataFusion() + iobjectdetection.object -> fuse.imageobject + lobjectdetection.object -> fuse.LIDARobject - sem = new Semantics(); - fuse.object -> sem.fusedObject; + sem = new Semantics() + fuse.object -> sem.fusedObject - ac = new Actuator(); - sem.actuation -> ac.actuation; + ac = new Actuator() + sem.actuation -> ac.actuation // An alternative path that activates the safety brake system based on LIDAR input - sb = new SafetyBrake(); - l.pointcloud -> sb.LIDARPointCloud; + sb = new SafetyBrake() // sb.actuation -> ac.actuation; // Cannot construct the alternative path because actuation may only be connected to a single upstream port - + l.pointcloud -> sb.LIDARPointCloud } diff --git a/experiments/C/src/Controller/ResponseTime.lf b/experiments/C/src/Controller/ResponseTime.lf index b0459eda..cf144770 100644 --- a/experiments/C/src/Controller/ResponseTime.lf +++ b/experiments/C/src/Controller/ResponseTime.lf @@ -1,28 +1,30 @@ /** - * This Lingua Franca (LF) program models a real-time control system consisting of - * a physical plant, a controller, and a planner. The physical plant produces - * sensor data at regular intervals, which are then sent to the controller. The - * controller sends this sensor data to the planner, which takes a brief pause to - * plan and then sends back a response, mimicking the latency often encountered in - * real-world control systems. The controller then sends a control signal back to - * the physical plant based on this response. The program also monitors the time - * taken for the control signal to reach the physical plant from the controller and - * issues a warning if the signal transmission time exceeds a certain threshold. - * This setup mimics a real-time, latency-sensitive, feedback control system with - * planning involved, such as those found in industrial automation or robotics. + * This Lingua Franca (LF) program models a real-time control system consisting of a physical plant, + * a controller, and a planner. The physical plant produces sensor data at regular intervals, which + * are then sent to the controller. The controller sends this sensor data to the planner, which + * takes a brief pause to plan and then sends back a response, mimicking the latency often + * encountered in real-world control systems. The controller then sends a control signal back to the + * physical plant based on this response. The program also monitors the time taken for the control + * signal to reach the physical plant from the controller and issues a warning if the signal + * transmission time exceeds a certain threshold. This setup mimics a real-time, latency-sensitive, + * feedback control system with planning involved, such as those found in industrial automation or + * robotics. */ target C { timeout: 1 sec } + reactor PhysicalPlant { - input control:double; - output sensor:double; - timer t(0, 33 msec); - state last_sensor_time:time(0); + input control: double + output sensor: double + timer t(0, 33 msec) + state last_sensor_time: time = 0 + reaction(t) -> sensor {= lf_set(sensor, 42); self->last_sensor_time = lf_time_physical(); =} + reaction(control) {= instant_t control_time = lf_time_physical(); lf_print("Latency %lld.", control_time - self->last_sensor_time); @@ -30,35 +32,40 @@ reactor PhysicalPlant { lf_print_warning("STP violation."); =} } + reactor Controller { - input sensor:double; - output control:double; - - output request_for_planning:double; - input planning:double; - + input sensor: double + output control: double + + output request_for_planning: double + input planning: double + reaction(sensor) -> request_for_planning {= lf_set(request_for_planning, sensor->value); =} + reaction(planning) -> control {= lf_set(control, planning->value); =} } + reactor Planner { - input request:double; - output response: double; + input request: double + output response: double + reaction(request) -> response {= lf_nanosleep(MSEC(10)); lf_set(response, request->value); =} } + main reactor { - p = new PhysicalPlant(); - c = new Controller(); - pl = new Planner(); - - p.sensor -> c.sensor; - c.request_for_planning -> pl.request; - pl.response -> c.planning; - c.control -> p.control; + p = new PhysicalPlant() + c = new Controller() + pl = new Planner() + + p.sensor -> c.sensor + c.request_for_planning -> pl.request + pl.response -> c.planning + c.control -> p.control } diff --git a/experiments/C/src/Controller/ResponseTime2.lf b/experiments/C/src/Controller/ResponseTime2.lf index f739e42d..1004491a 100644 --- a/experiments/C/src/Controller/ResponseTime2.lf +++ b/experiments/C/src/Controller/ResponseTime2.lf @@ -1,32 +1,33 @@ /** - * This Lingua Franca (LF) program represents a real-time control system composed - * of a physical plant, a controller, and a planner. The physical plant generates - * sensor data at regular intervals and sends this data to the controller. The - * controller forwards the sensor data to the planner, which simulates latency - * through a brief sleep period before sending a response back to the controller. - * The controller maintains the latest control value received from the planner and - * sends it back to the physical plant as a control signal after receiving the next - * sensor data. Additionally, the program calculates and displays the latency - * between sensor readings and the control signal transmission. It also monitors - * the Signal Transmission Period (STP) and issues a warning if a violation occurs. - * Overall, this program simulates a real-time, feedback control system with - * planning, similar to those found in industrial automation or robotics, while - * accounting for latency and timing constraints. + * This Lingua Franca (LF) program represents a real-time control system composed of a physical + * plant, a controller, and a planner. The physical plant generates sensor data at regular intervals + * and sends this data to the controller. The controller forwards the sensor data to the planner, + * which simulates latency through a brief sleep period before sending a response back to the + * controller. The controller maintains the latest control value received from the planner and sends + * it back to the physical plant as a control signal after receiving the next sensor data. + * Additionally, the program calculates and displays the latency between sensor readings and the + * control signal transmission. It also monitors the Signal Transmission Period (STP) and issues a + * warning if a violation occurs. Overall, this program simulates a real-time, feedback control + * system with planning, similar to those found in industrial automation or robotics, while + * accounting for latency and timing constraints. */ target C { timeout: 1 sec } + reactor PhysicalPlant { - input control:double; - output sensor:double; - timer t(0, 33 msec); - state last_sensor_time:time(0); - state previous_sensor_time:time(0); + input control: double + output sensor: double + timer t(0, 33 msec) + state last_sensor_time: time = 0 + state previous_sensor_time: time = 0 + reaction(t) -> sensor {= lf_set(sensor, 42); self->previous_sensor_time = self->last_sensor_time; self->last_sensor_time = lf_time_physical(); =} + reaction(control) {= instant_t control_time = lf_time_physical(); lf_print("Latency %lld.", control_time - self->previous_sensor_time); @@ -35,19 +36,21 @@ reactor PhysicalPlant { lf_print_warning("STP violation."); =} } + reactor Controller { - input sensor:double; - output control:double; - - state latest_control:double(0.0); - state first:bool(true); - - output request_for_planning:double; - input planning:double; - + input sensor: double + output control: double + + state latest_control: double = 0.0 + state first: bool = true + + output request_for_planning: double + input planning: double + reaction(planning) {= self->latest_control = planning->value; =} + reaction(sensor) -> control, request_for_planning {= if (!self->first) { lf_set(control, self->latest_control); @@ -56,21 +59,24 @@ reactor Controller { lf_set(request_for_planning, sensor->value); =} } + reactor Planner { - input request:double; - output response: double; + input request: double + output response: double + reaction(request) -> response {= lf_nanosleep(MSEC(10)); lf_set(response, request->value); =} } + main reactor { - p = new PhysicalPlant(); - c = new Controller(); - pl = new Planner(); - - p.sensor -> c.sensor; - c.request_for_planning -> pl.request; - pl.response -> c.planning after 0; - c.control -> p.control; + p = new PhysicalPlant() + c = new Controller() + pl = new Planner() + + p.sensor -> c.sensor + c.request_for_planning -> pl.request + pl.response -> c.planning after 0 + c.control -> p.control } diff --git a/experiments/C/src/Controller/ResponseTime3.lf b/experiments/C/src/Controller/ResponseTime3.lf index a6c6eea3..5d2ec0a2 100644 --- a/experiments/C/src/Controller/ResponseTime3.lf +++ b/experiments/C/src/Controller/ResponseTime3.lf @@ -1,19 +1,19 @@ /** - * This Lingua Franca (LF) program simulates a digital control system with a - * feedback loop between a physical plant, a controller, and a planner. The - * physical plant periodically sends sensor data and computes latencies based on - * control inputs, also alerting when a certain synchronization tolerance property - * (STP) is violated. The controller, which receives the sensor data, communicates - * with the planner before sending control signals back to the physical plant, - * skipping the first control action. The planner, upon receiving a request, takes - * some time to respond with the same value as the request. The program simulates - * real-time control mechanisms common in industrial automation, robotics, and - * embedded systems, including latency measurements and STP checks. + * This Lingua Franca (LF) program simulates a digital control system with a feedback loop between a + * physical plant, a controller, and a planner. The physical plant periodically sends sensor data + * and computes latencies based on control inputs, also alerting when a certain synchronization + * tolerance property (STP) is violated. The controller, which receives the sensor data, + * communicates with the planner before sending control signals back to the physical plant, skipping + * the first control action. The planner, upon receiving a request, takes some time to respond with + * the same value as the request. The program simulates real-time control mechanisms common in + * industrial automation, robotics, and embedded systems, including latency measurements and STP + * checks. */ target C { timeout: 1 sec, keepalive: true } + reactor PhysicalPlant { preamble {= void* my_thread(void* a) { @@ -23,20 +23,23 @@ reactor PhysicalPlant { } } =} - input control:double; - output sensor:double; - state last_sensor_time:time(0); - state previous_sensor_time:time(0); - state thread_id:lf_thread_t(0); - physical action a:int; + input control: double + output sensor: double + state last_sensor_time: time = 0 + state previous_sensor_time: time = 0 + state thread_id: lf_thread_t = 0 + physical action a: int + reaction(startup) -> a {= lf_thread_create(&self->thread_id, &my_thread, a); =} + reaction(a) -> sensor {= lf_set(sensor, 42); self->previous_sensor_time = self->last_sensor_time; self->last_sensor_time = lf_time_logical(); =} + reaction(control) {= instant_t control_time = lf_time_physical(); lf_print("Latency %lld.", control_time - self->previous_sensor_time); @@ -46,21 +49,23 @@ reactor PhysicalPlant { lf_print_warning("STP violation."); =} } + reactor Controller { - input sensor:double; - output control:double; - - state latest_control:double(0.0); - state first:bool(true); - state in_progress:bool(false); - - output request_for_planning:double; - input planning:double; - + input sensor: double + output control: double + + state latest_control: double = 0.0 + state first: bool = true + state in_progress: bool = false + + output request_for_planning: double + input planning: double + reaction(planning) {= self->latest_control = planning->value; self->in_progress = false; =} + reaction(sensor) -> control, request_for_planning {= if (!self->first) { lf_set(control, self->latest_control); @@ -73,21 +78,24 @@ reactor Controller { } =} } + reactor Planner { - input request:double; - output response: double; + input request: double + output response: double + reaction(request) -> response {= lf_nanosleep(MSEC(100)); lf_set(response, request->value); =} } + main reactor { - p = new PhysicalPlant(); - c = new Controller(); - pl = new Planner(); - - p.sensor -> c.sensor; - c.request_for_planning -> pl.request; - pl.response -> c.planning after 100 msec; - c.control -> p.control; + p = new PhysicalPlant() + c = new Controller() + pl = new Planner() + + p.sensor -> c.sensor + c.request_for_planning -> pl.request + pl.response -> c.planning after 100 msec + c.control -> p.control } diff --git a/experiments/C/src/Intersection/Intersection.lf b/experiments/C/src/Intersection/Intersection.lf index 1a07f565..4e3b34a3 100644 --- a/experiments/C/src/Intersection/Intersection.lf +++ b/experiments/C/src/Intersection/Intersection.lf @@ -1,16 +1,13 @@ /** - * Model of a smart intersection with a road-side unit (RSU) - * that regulates the flow of automated vehicles through the - * intersection. Vehicles that are approaching the intersection - * send an initial message to the RSU with their speed and - * distance to the intersection. The RSU responds with a - * reservation for when the vehicle can enter the intersection - * and what its average speed through the intersection should be. - * - * This is meant as a supervisory controller, and it assumes that - * the vehicle is equipped with a low-level controller (or a human) - * that is responsible for lane keeping, collision avoidance, etc. - * + * Model of a smart intersection with a road-side unit (RSU) that regulates the flow of automated + * vehicles through the intersection. Vehicles that are approaching the intersection send an initial + * message to the RSU with their speed and distance to the intersection. The RSU responds with a + * reservation for when the vehicle can enter the intersection and what its average speed through + * the intersection should be. + * + * This is meant as a supervisory controller, and it assumes that the vehicle is equipped with a + * low-level controller (or a human) that is responsible for lane keeping, collision avoidance, etc. + * * This is a very rough starting point that needs a lot of work. */ target C { @@ -22,14 +19,14 @@ preamble {= double speed; double distance; } request_message_t; - + typedef struct { // Average speed vehicle should maintain in the intersection. double target_speed; // FIXME: Deadline. = t/w // Time at which the vehicle can enter the intersection. instant_t arrival_time; } grant_message_t; - + // Table of offsets by vehicle bank_index: interval_t timer_offsets[] = { 0LL, @@ -46,18 +43,18 @@ preamble {= }; =} -reactor Vehicle ( - offset:time(0), - period:time(1 sec), - speed:double(42.0), // in km per hour. About 11.7 m/sec - distance:double(42.0) // in meters. About 4 sec to traverse. -) { - input grant:grant_message_t; - - output request:request_message_t; - - logical action delay; - +reactor Vehicle( + offset: time = 0, + period: time = 1 sec, + speed: double = 42.0, // in km per hour. About 11.7 m/sec + // in meters. About 4 sec to traverse. + distance: double = 42.0) { + input grant: grant_message_t + + output request: request_message_t + + logical action delay + reaction(startup) -> request, delay {= if (timer_offsets[self->bank_index] == 0LL) { // Need to send a message at the start time. @@ -78,7 +75,7 @@ reactor Vehicle ( lf_set(request, message); lf_schedule(delay, timer_periods[self->bank_index]); =} - + reaction(grant) {= lf_print("Granted access at elapsed logical time %lld. Physical time is %lld", lf_time_logical_elapsed(), @@ -87,18 +84,18 @@ reactor Vehicle ( =} } -reactor RSU ( - num_entries:int(4), - intersection_width:double(42.0), // in meters. - // If the vehicle is told to slow down, then its target - // average speed in the intersection should be at least this. - nominal_speed_in_intersection:double(10.0) // In km/hr. 2.8 m/sec. 15 sec to traverse. -) { - input[num_entries] request:request_message_t; - output[num_entries] grant:grant_message_t; - - state earliest_free:time(0); - +reactor RSU( + num_entries: int = 4, + intersection_width: double = 42.0, // in meters. + // If the vehicle is told to slow down, then its target + // average speed in the intersection should be at least this. + // In km/hr. 2.8 m/sec. 15 sec to traverse. + nominal_speed_in_intersection: double = 10.0) { + input[num_entries] request: request_message_t + output[num_entries] grant: grant_message_t + + state earliest_free: time = 0 + reaction(request) -> grant {= for (int i = 0; i < self->num_entries; i++) { if (request[i]->is_present) { @@ -108,12 +105,12 @@ reactor RSU ( // according to the arriving vehicle's clock. double speed_in_m_per_sec = request[i]->value.speed * 1000.0 / 3600.0; double arrival_in = request[i]->value.distance / speed_in_m_per_sec; - + instant_t time_message_sent = lf_time_logical(); - + // Convert the time interval to nsec (it is seconds). interval_t arrival_time_ns = time_message_sent + (interval_t) (arrival_in * BILLION); - + grant_message_t response; if (arrival_time_ns >= self->earliest_free) { // Vehicle can maintain speed. @@ -128,12 +125,12 @@ reactor RSU ( lf_set(grant[i], response); // Update earliest free on the assumption that the vehicle // maintains its target speed (on average) within the intersection. - interval_t time_in_intersection - = (interval_t)(BILLION * self->intersection_width * 3600 + interval_t time_in_intersection + = (interval_t)(BILLION * self->intersection_width * 3600 / (1000 * response.target_speed) ); self->earliest_free = response.arrival_time + time_in_intersection; - + lf_print("*** Grant access to vehicle %d to enter at time %lld. Next available time is %lld", i, response.arrival_time - lf_time_start(), @@ -145,9 +142,9 @@ reactor RSU ( } main reactor { - vehicles = new[4] Vehicle(offset = 0); - - rsu = new RSU(); - vehicles.request -> rsu.request; - rsu.grant -> vehicles.grant; + vehicles = new[4] Vehicle(offset=0) + + rsu = new RSU() + vehicles.request -> rsu.request + rsu.grant -> vehicles.grant } diff --git a/experiments/C/src/Logic/Logic.lf b/experiments/C/src/Logic/Logic.lf index 3f834e11..1a0fafa4 100644 --- a/experiments/C/src/Logic/Logic.lf +++ b/experiments/C/src/Logic/Logic.lf @@ -1,26 +1,27 @@ -target C {timeout: 200 msec}; +target C { + timeout: 200 msec +} // Test illustrating that the current implementation of lf_schedule leads to unexpected behavior. +reactor Source(name: string = "source", even: bool = true) { + output out: bool + state on: bool = false + state count: int = 0 + logical action next -reactor Source(name:string("source"), even:bool(true)) { - output out:bool; - state on:bool(false); - state count:int(0); - logical action next; - reaction(startup) -> out {= if (!self->even) { self->on = true; } =} - + reaction(startup, next) -> next, out {= if (self->on) { lf_set(out, true); } - + self->on = !self->on; - + if (self->count < 9) { self->count++; lf_schedule(next, 0); @@ -32,23 +33,23 @@ reactor Source(name:string("source"), even:bool(true)) { } reactor LogicalAnd { - input a:bool; - input b:bool; - + input a: bool + input b: bool + reaction(a, b) {= - printf("Output: %d, tag: (%lld, %u)\n", + printf("Output: %d, tag: (%lld, %u)\n", a->is_present & b->is_present, lf_time_logical_elapsed(), lf_tag().microstep ); =} } main reactor Logic { - x = new Source(name="x", even=true); - y = new Source(name="y", even=false); - z = new LogicalAnd(); - - x.out -> z.a after 1 msec; - y.out -> z.b after 1 msec; - //x.out -> z.a; - //y.out -> z.b; + x = new Source(name="x", even=true) + y = new Source(name="y", even=false) + z = new LogicalAnd() + + x.out -> z.a after 1 msec + // x.out -> z.a; + // y.out -> z.b; + y.out -> z.b after 1 msec } diff --git a/experiments/C/src/MarsHelicopter/MarsHelicopter.lf b/experiments/C/src/MarsHelicopter/MarsHelicopter.lf new file mode 100644 index 00000000..541ecd56 --- /dev/null +++ b/experiments/C/src/MarsHelicopter/MarsHelicopter.lf @@ -0,0 +1,122 @@ +/** + * This example is based on the block diagram shown in the Mars Helicopter Talk at FSW 2022. + * https://www.youtube.com/watch?v=D-Y6H0GMtbM&t=465s + * + * @author Shaokai Lin + */ +target C + +// Sony IMX214 13MP Color +// https://www.supertekmodule.com/product-item/13mp-camera-module-imx214/ +reactor CameraColor { + output out: {= camera_color_t =} + timer t(0, 33 ms) // ~30 FPS + + reaction(t) -> out {= =} +} + +// Omnivision OV7251 B&W +// https://www.ovt.com/products/ov7251/ +reactor CameraBW { + output out: {= camera_bw_t =} + timer t(0, 8 ms) // ~120 FPS + + reaction(t) -> out {= =} +} + +// Zigbee Radio +// https://development.libelium.com/zigbee-networking-guide/code-examples-and-extended-information +reactor ZigbeeRadio { + output out: {= radio_t =} + physical action receivePacket + timer t(0, 5 sec) + + reaction(receivePacket) -> out {= =} + + reaction(t) {= =} +} + +// Processor for fusing images +reactor ImageProcessor { + input fromRadio: {= radio_t =} + input fromCameraColor: {= camera_color_t =} + input fromCameraBW: {= camera_bw_t =} + output objectsDetected: {= object_t =} + logical action infer(10 msec) + state opMode: int = 0 + + reaction(fromRadio) {= =} // Perhaps incoming packets can change opMode, the mode of operation? + + reaction(fromCameraColor, fromCameraBW) -> infer {= =} // Sensor fusion + + // Run neural network inference, output objectsDetected. + // FIXME: Not sure if this is a reasonable design. Is Ingenuity actually + // doing object detection when flying? + reaction(infer) -> objectsDetected {= =} +} + +// Motors +reactor Motors { + input in: {= motor_command_t =} + + reaction(in) {= =} +} + +// Garmin Lidar Lite LRF +// https://www.garmin.com/en-US/p/557294#specs +reactor Lidar { + output out: {= lidar_t =} + timer t(0, 2 ms) // 500 HZ + + reaction(t) -> out {= =} +} + +// Bosch BMI-160 IMU +// https://www.mouser.com/datasheet/2/783/BST_BMI160_DS000-1509569.pdf +reactor IMU { + output out: {= imu_t =} + timer t(0, 10 ms) // 100 HZ Output Data Rate (ODR) + + reaction(t) -> out {= =} +} + +// muRata SCA100T inclinometer +// https://www.murata.com/en-us/api/pdfdownloadapi?cate=cgsubAccelerometers&partno=SCA100T-D07 +reactor Inclinometer { + output out: {= inclinometer_t =} + timer t(0, 2500 us) // 400 HZ + + reaction(t) -> out {= =} +} + +// A central controller for generating output commands +reactor Controller { + input fromImageProcessor: {= object_t =} + input fromLidar: {= lidar_t =} + input fromIMU: {= imu_t =} + input fromInclinometer: {= inclinometer_t =} + output toMotor: {= motor_command_t =} + + reaction(fromImageProcessor, fromLidar, fromIMU, fromInclinometer) -> toMotor {= =} +} + +main reactor { + cameraColor = new CameraColor() + cameraBW = new CameraBW() + radio = new ZigbeeRadio() + imageProcessor = new ImageProcessor() + motors = new Motors() + lidar = new Lidar() + imu = new IMU() + inclinometer = new Inclinometer() + controller = new Controller() + + cameraColor.out -> imageProcessor.fromCameraColor + cameraBW.out -> imageProcessor.fromCameraBW + radio.out -> imageProcessor.fromRadio + imageProcessor.objectsDetected -> controller.fromImageProcessor + lidar.out -> controller.fromLidar + imu.out -> controller.fromIMU + inclinometer.out -> controller.fromInclinometer + controller.toMotor -> motors.in +} diff --git a/experiments/C/src/Microsteps/Anomaly.lf b/experiments/C/src/Microsteps/Anomaly.lf index 6e0138b1..fbb34e74 100644 --- a/experiments/C/src/Microsteps/Anomaly.lf +++ b/experiments/C/src/Microsteps/Anomaly.lf @@ -1,19 +1,21 @@ - /** - * This program illustrates the strangeness of the way microsteps are currently handled - * (at least as of February, 2021) when scheduling in the future. The two outputs of the - * Source reactor s are never simultaneous. At the Destination reactor d1, they are also - * never simultaneous, but at d2, they are always simultaneous. - * +/** + * This program illustrates the strangeness of the way microsteps are currently handled (at least as + * of February, 2021) when scheduling in the future. The two outputs of the Source reactor s are + * never simultaneous. At the Destination reactor d1, they are also never simultaneous, but at d2, + * they are always simultaneous. + * * @author Edward A. Lee */ target C { - timeout: 50 msec, + timeout: 50 msec } + reactor Source { - output out1:int; - output out2:int; - logical action redo; - state count:int(0); + output out1: int + output out2: int + logical action redo + state count: int = 0 + reaction(startup, redo) -> out1, out2, redo {= if (self->count++ < 4) { if (self->count % 2 == 0) { @@ -31,12 +33,14 @@ reactor Source { } =} } -reactor Destination(name:string("dest")) { - input in1:int; - input in2:int; - reaction (in1, in2) {= + +reactor Destination(name: string = "dest") { + input in1: int + input in2: int + + reaction(in1, in2) {= if (in1->is_present) { - lf_print("%s: Tag (%lld, %d): in1 received %d", self->name, + lf_print("%s: Tag (%lld, %d): in1 received %d", self->name, lf_time_logical_elapsed(), lf_tag().microstep, in1->value ); } @@ -47,12 +51,13 @@ reactor Destination(name:string("dest")) { } =} } + main reactor Anomaly { - s = new Source(); - d1 = new Destination(name = "d1"); - d2 = new Destination(name = "d2"); - s.out1 -> d1.in1; - s.out2 -> d1.in2; - s.out1 -> d2.in1 after 5 msec; - s.out2 -> d2.in2 after 5 msec; + s = new Source() + d1 = new Destination(name="d1") + d2 = new Destination(name="d2") + s.out1 -> d1.in1 + s.out2 -> d1.in2 + s.out1 -> d2.in1 after 5 msec + s.out2 -> d2.in2 after 5 msec } diff --git a/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes.lf b/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes.lf index 7cf3a13c..e8a375d8 100644 --- a/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes.lf +++ b/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes.lf @@ -1,47 +1,47 @@ /** - * The provided Lingua Franca (LF) program models a simple decision-making system - * that transitions between different modes of operation based on certain implied - * conditions. The program starts in the HandleGhostClose mode, where it - * instantiates an instance of GhostCloseBehavior. This GhostCloseBehavior reactor - * toggles between two modes, ChaseGhost and AvoidGhost, representing different - * behaviors. In the main reactor, if the implied condition GhostClose becomes - * true, the program transitions to the EatPills mode. In this mode, it executes an - * action represented as EatPills, and if the condition changes to GhostClose - * again, it reverts back to the HandleGhostClose mode, resetting the state. The - * program essentially demonstrates a simple state machine that switches between - * different states or behaviors based on the changes in the assumed conditions. + * The provided Lingua Franca (LF) program models a simple decision-making system that transitions + * between different modes of operation based on certain implied conditions. The program starts in + * the HandleGhostClose mode, where it instantiates an instance of GhostCloseBehavior. This + * GhostCloseBehavior reactor toggles between two modes, ChaseGhost and AvoidGhost, representing + * different behaviors. In the main reactor, if the implied condition GhostClose becomes true, the + * program transitions to the EatPills mode. In this mode, it executes an action represented as + * EatPills, and if the condition changes to GhostClose again, it reverts back to the + * HandleGhostClose mode, resetting the state. The program essentially demonstrates a simple state + * machine that switches between different states or behaviors based on the changes in the assumed + * conditions. */ -target C; +target C reactor GhostCloseBehavior { initial mode ChaseGhost { @label("Condition: !GhostScared") - reaction() -> AvoidGhost {==} - + reaction() -> reset(AvoidGhost) {= =} + @label("Action: ChaseGhost") - reaction() {==} + reaction() {= =} } + mode AvoidGhost { @label("Condition: GhostScared") - reaction() -> ChaseGhost {==} - + reaction() -> reset(ChaseGhost) {= =} + @label("Action: AvoidGhost") - reaction() {==} + reaction() {= =} } } main reactor { initial mode HandleGhostClose { - @label("Condition: !GhostClose") - reaction() -> EatPills {==} - inner = new GhostCloseBehavior() + @label("Condition: !GhostClose") + reaction() -> reset(EatPills) {= =} } + mode EatPills { @label("Condition: GhostClose") - reaction() -> reset(HandleGhostClose) {==} - + reaction() -> reset(HandleGhostClose) {= =} + @label("Action: EatPills") - reaction() {==} + reaction() {= =} } } diff --git a/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes_Triggers.lf b/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes_Triggers.lf index 9a08e960..353cf921 100644 --- a/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes_Triggers.lf +++ b/experiments/C/src/ModalModels/BehaviorTrees/pacman/PacMan_BehaviorTree_Mockup_Modes_Triggers.lf @@ -1,54 +1,53 @@ /** - * The given Lingua Franca (LF) program models the behavior of Pac-Man in a game - * environment. The program takes into account two input conditions: the proximity - * of ghosts (GhostClose) and whether the ghosts are scared (GhostScared). The - * behavior of Pac-Man is represented by two modes, HandleGhostClose and EatPills. - * When a ghost is close, Pac-Man enters the HandleGhostClose mode, which further - * branches into two sub-modes, ChaseGhost and AvoidGhost, based on whether the - * ghosts are scared or not. In this mode, Pac-Man either chases the scared ghosts - * or avoids them. On the other hand, when no ghosts are in close proximity, - * Pac-Man operates in the EatPills mode, focusing on consuming the pills in the - * game environment. The program provides a high-level simulation of Pac-Man's - * decision-making process based on the game's dynamic conditions. + * The given Lingua Franca (LF) program models the behavior of Pac-Man in a game environment. The + * program takes into account two input conditions: the proximity of ghosts (GhostClose) and whether + * the ghosts are scared (GhostScared). The behavior of Pac-Man is represented by two modes, + * HandleGhostClose and EatPills. When a ghost is close, Pac-Man enters the HandleGhostClose mode, + * which further branches into two sub-modes, ChaseGhost and AvoidGhost, based on whether the ghosts + * are scared or not. In this mode, Pac-Man either chases the scared ghosts or avoids them. On the + * other hand, when no ghosts are in close proximity, Pac-Man operates in the EatPills mode, + * focusing on consuming the pills in the game environment. The program provides a high-level + * simulation of Pac-Man's decision-making process based on the game's dynamic conditions. */ -target C; +target C reactor GhostCloseBehavior { - input GhostScared:bool - + input GhostScared: bool + initial mode ChaseGhost { @label("Condition: !GhostScared") - reaction(reset, GhostScared) -> AvoidGhost {==} - + reaction(reset, GhostScared) -> reset(AvoidGhost) {= =} + @label("Action: ChaseGhost") - reaction(reset) {==} + reaction(reset) {= =} } + mode AvoidGhost { @label("Condition: GhostScared") - reaction(GhostScared) -> ChaseGhost {==} - + reaction(GhostScared) -> reset(ChaseGhost) {= =} + @label("Action: AvoidGhost") - reaction(reset) {==} + reaction(reset) {= =} } } reactor PacManBehavior { - input GhostClose:bool - input GhostScared:bool - + input GhostClose: bool + input GhostScared: bool + initial mode HandleGhostClose { - @label("Condition: !GhostClose") - reaction(GhostClose) -> EatPills {==} - inner = new GhostCloseBehavior() GhostScared -> inner.GhostScared + @label("Condition: !GhostClose") + reaction(GhostClose) -> reset(EatPills) {= =} } + mode EatPills { @label("Condition: GhostClose") - reaction(GhostClose) -> reset(HandleGhostClose) {==} - + reaction(GhostClose) -> reset(HandleGhostClose) {= =} + @label("Action: EatPills") - reaction(reset) {==} + reaction(reset) {= =} } } diff --git a/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_advanced.lf b/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_advanced.lf index 489e05ff..02748012 100644 --- a/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_advanced.lf +++ b/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_advanced.lf @@ -1,32 +1,26 @@ -/* - * Implements a modal LF version of the hierarchical statemachine - * for the behavior tree in presented in this article: - * https://robohub.org/introduction-to-behavior-trees/ - * - * Compared to the simple variant this uses modes more extensively, which - * results in the correct behavior. - * Moreover, modeling the sequence in Nominal as modal enables the potential - * use of a history transition that could allow modeling the continuation - * of the task sequence at the point where it was left when the battery ran out. +/** + * Implements a modal LF version of the hierarchical statemachine for the behavior tree in presented + * in this article: https://robohub.org/introduction-to-behavior-trees/ + * + * Compared to the simple variant this uses modes more extensively, which results in the correct + * behavior. Moreover, modeling the sequence in Nominal as modal enables the potential use of a + * history transition that could allow modeling the continuation of the task sequence at the point + * where it was left when the battery ran out. */ -target C { -// logging: debug -} +target C + +reactor GenericTask(name: string = "") { + output success: bool + output failure: bool -reactor GenericTask(name:string("")) { - output success:bool - output failure:bool - initial mode Running { - // Just for testing - timer work(0, 250msec) - timer finish(1sec, 1sec) - + timer work(0, 250 msec) // Just for testing + timer finish(1 sec, 1 sec) reaction(work) {= printf("%s\n", self->name); =} - - reaction(finish) -> success, Succeeded, failure, Failed {= + + reaction(finish) -> success, reset(Succeeded), failure, reset(Failed) {= int r = rand() % 6; if (r == 0) { lf_set(failure, true); @@ -37,42 +31,42 @@ reactor GenericTask(name:string("")) { } =} } - - mode Succeeded {} - mode Failed {} + + mode Succeeded { + } + + mode Failed { + } } reactor NominalBehavior { - input BatteryOK:bool - - output success:bool - output failure:bool - + input BatteryOK: bool + + output success: bool + output failure: bool + initial mode MoveToObj { MoveToObjTask = new GenericTask(name="MoveToObj") - + MoveToObjTask.failure -> failure - - reaction(MoveToObjTask.success) -> CloseGrip {= + reaction(MoveToObjTask.success) -> reset(CloseGrip) {= lf_set_mode(CloseGrip); =} } - + mode CloseGrip { CloseGripTask = new GenericTask(name="CloseGrip") - + CloseGripTask.failure -> failure - - reaction(CloseGripTask.success) -> MoveHome {= + reaction(CloseGripTask.success) -> reset(MoveHome) {= lf_set_mode(MoveHome); =} } - + mode MoveHome { MoveHomeTask = new GenericTask(name="MoveHome") - + MoveHomeTask.failure -> failure - reaction(MoveHomeTask.success) -> success {= lf_set(success, true); =} @@ -80,31 +74,29 @@ reactor NominalBehavior { } reactor Robot { - input BatteryOK:bool - - output success:bool - output failure:bool - + input BatteryOK: bool + + output success: bool + output failure: bool + initial mode Nominal { NominalBehavior = new NominalBehavior() - + NominalBehavior.success -> success NominalBehavior.failure -> failure - - reaction(BatteryOK) -> Charging {= + reaction(BatteryOK) -> reset(Charging) {= if (!BatteryOK->value) { lf_set_mode(Charging); printf("Battery empty\n"); } =} } - + mode Charging { GoCharge = new GenericTask(name="GoCharge") - + GoCharge.failure -> failure - - reaction(BatteryOK, GoCharge.success) -> Nominal {= + reaction(BatteryOK, GoCharge.success) -> reset(Nominal) {= // Assumes simultaneous presence if (BatteryOK->value && GoCharge.success->value) { lf_set_mode(Nominal); @@ -115,11 +107,11 @@ reactor Robot { } main reactor { - timer Battery(1sec, 1sec) - state battery_state:int(1) - + timer Battery(1 sec, 1 sec) + state battery_state: int = 1 + robot = new Robot() - + reaction(Battery) -> robot.BatteryOK {= self->battery_state--; lf_set(robot.BatteryOK, self->battery_state > 0); @@ -127,15 +119,14 @@ main reactor { self->battery_state = 5; } =} - + reaction(robot.success) {= printf("Total success\n"); lf_request_stop(); =} - + reaction(robot.failure) {= printf("Utter failure\n"); lf_request_stop(); =} - } diff --git a/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_simple.lf b/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_simple.lf index e15d12b0..0e626bae 100644 --- a/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_simple.lf +++ b/experiments/C/src/ModalModels/BehaviorTrees/robohub_example_simple.lf @@ -1,23 +1,21 @@ -/* - * Implements a modal LF version of the hierarchical statemachine - * for the behavior tree in presented in this article: - * https://robohub.org/introduction-to-behavior-trees/ - * - * It implements the core behavior sequence by chaining up reactors. - * However, this currently does not work correctly with modes because - * there is no support for reacting to entering a mode. - * Hence, when switching to Charging the task is not started and if - * switching back to Nominal the sequence would not restart. +/** + * Implements a modal LF version of the hierarchical statemachine for the behavior tree in presented + * in this article: https://robohub.org/introduction-to-behavior-trees/ + * + * It implements the core behavior sequence by chaining up reactors. However, this currently does + * not work correctly with modes because there is no support for reacting to entering a mode. Hence, + * when switching to Charging the task is not started and if switching back to Nominal the sequence + * would not restart. */ -target C; +target C + +reactor GenericTask(name: string = "") { + input start: bool + output success: bool + output failure: bool -reactor GenericTask(name:string("")) { - input start:bool - output success:bool - output failure:bool - logical action continue_task - + reaction(start, continue_task) -> continue_task, success, failure {= printf("%s\n", self->name); int r = rand() % 10; @@ -32,50 +30,46 @@ reactor GenericTask(name:string("")) { } reactor Robot { - input start:bool - input BatteryOK:bool - output success:bool - output failure:bool - + input start: bool + input BatteryOK: bool + output success: bool + output failure: bool + initial mode Nominal { MoveToObj = new GenericTask(name="MoveToObj") CloseGrip = new GenericTask(name="CloseGrip") MoveHome = new GenericTask(name="MoveHome") - - start -> MoveToObj.start // No resume after charging + + start -> MoveToObj.start // No resume after charging MoveToObj.success -> CloseGrip.start CloseGrip.success -> MoveHome.start MoveHome.success -> success - + MoveToObj.failure -> failure CloseGrip.failure -> failure MoveHome.failure -> failure - // Potential solution for resuming after charging -// reaction(entry) -> MoveToObj.start {= -// // PROBLEM!! -// lf_set(MoveToObj.start, true); -// =} - - reaction(BatteryOK) -> Charging {= + // reaction(entry) -> MoveToObj.start {= + // // PROBLEM!! + // lf_set(MoveToObj.start, true); + // =} + reaction(BatteryOK) -> reset(Charging) {= if (!BatteryOK->value) { lf_set_mode(Charging); printf("Battery empty\n"); } =} } - + mode Charging { GoCharge = new GenericTask(name="GoCharge") - + GoCharge.failure -> failure - // Potential solution for starting task when mode is entered because no start event is provided -// reaction(entry) -> GoCharge.start {= -// lf_set(GoCharge.start, true); -// =} - - reaction(BatteryOK, GoCharge.success) -> Nominal {= + // reaction(entry) -> GoCharge.start {= + // lf_set(GoCharge.start, true); + // =} + reaction(BatteryOK, GoCharge.success) -> reset(Nominal) {= // Assumes simultaneous presence if (BatteryOK->value && GoCharge.success->value) { lf_set_mode(Nominal); @@ -86,11 +80,11 @@ reactor Robot { } main reactor { - timer Battery(1sec, 1sec) - state battery_state:int(1) - + timer Battery(1 sec, 1 sec) + state battery_state: int = 1 + robot = new Robot() - + reaction(startup) -> robot.start {= lf_set(robot.start, true); =} @@ -102,15 +96,14 @@ main reactor { self->battery_state = 5; } =} - + reaction(robot.success) {= printf("Total success\n"); lf_request_stop(); =} - + reaction(robot.failure) {= printf("Utter failure\n"); lf_request_stop(); =} - } diff --git a/experiments/C/src/ModalModels/Motivation/Chrono/Chrono.lf b/experiments/C/src/ModalModels/Motivation/Chrono/Chrono.lf index 3871ec3d..44b27c3f 100644 --- a/experiments/C/src/ModalModels/Motivation/Chrono/Chrono.lf +++ b/experiments/C/src/ModalModels/Motivation/Chrono/Chrono.lf @@ -1,85 +1,80 @@ /** * This example illustrates a first idea for the use of modes. - * - * It is inspired by the Chronometer example by - * Jean-Louis Colaço, Bruno Pagano, Marc Pouzet, - * A conservative extension of synchronous data-flow with state machines, - * EMSOFT 2005. - * + * + * It is inspired by the Chronometer example by Jean-Louis Colaço, Bruno Pagano, Marc Pouzet, A + * conservative extension of synchronous data-flow with state machines, EMSOFT 2005. + * * The code was originally derived from ReflexGame.lf - * + * * @author Alexander Schulz-Rosengarten * @author Reinhard von Hanxleden */ target C { - threads: 1, + single-threaded: true, keepalive: true -}; - -/* POSSIBLE NEW SYNTAX FOR DESCRIBING MODES AT LF-LEVEL - * Still open questions include - * Q1: Do we want hierarchy, ie, mixing state/dataflow across levels as in Ptolemy/SCADE? - modes Stop, Start; // Q2: This is actually redundant - still useful? - - mode Stop() { ... // Q3: Should possible successor states ("Start") be listed here as well? - reaction(enter) {= - printf("Entered Stop!"); - } - - reaction(stst) -> Start {= // Q4: Should transitions be encoded as reactions? - changemode(Start); // Q5: Should this be hostcode? Should this be instantaneous? - =} - } - - mode Start() { ... - timer dTimer(10 msec, 10 msec); +} - reaction(enter) {= - printf("Entered Start!"); - } +/** + * POSSIBLE NEW SYNTAX FOR DESCRIBING MODES AT LF-LEVEL Still open questions include: + * --> Q1: Do we want hierarchy, ie, mixing state/dataflow across levels as in Ptolemy/SCADE? + * --> Q2: This is actually redundant - still useful? + * --> Q3: Should possible successor states ("Start") be listed here as well? + * --> Q4: Should transitions be encoded as reactions? + * --> Q5: Should this be hostcode? Should this be instantaneous? + * + * modes Stop, Start; + * + * mode Stop() { ... + * + * reaction(enter) {= printf("Entered Stop!"); =} + * + * reaction(stst) -> Start {= changemode(Start); =} + * + * } + * + * mode Start() { ... + * + * timer dTimer(10 msec, 10 msec); + * + * reaction(enter) {= printf("Entered Start!"); =} + * + * reaction(dTimer) -> d, s, m {= ... =} + * + * reaction(stst) -> Stop {= changemode(Stop); =} + * + * } + */ +reactor ChronoLogic { + input stst: int - reaction(dTimer) -> d, s, m {= ... =} - - reaction(stst) -> Stop {= - changemode(Stop); - =} - } + output m: int // time output + output s: int + output d: int -*/ + state dState: int = 0 // time state + state sState: int = 0 + state mState: int = 0 -reactor ChronoLogic { - input stst:int; - - // time output - output m:int; - output s:int; - output d:int; - - // time state - state dState:int(0); - state sState:int(0); - state mState:int(0); - // active state - state ststState:int(0); // 0 = STOP, 1 = START - - // provide base time - timer dTimer(10 msec, 10 msec); - + // 0 = STOP, 1 = START + state ststState: int = 0 + + timer dTimer(10 msec, 10 msec) // provide base time + reaction(startup) {= printf("'x' + 'Enter' kills the program.\n"); printf("Just 'Enter' alternates between modes STOP and START.\n\n"); =} - + reaction(stst) {= self->ststState = 1 - self->ststState; if (self->ststState) { printf("Entered START!\n"); } else { - printf("Entered STOP!\n"); + printf("Entered STOP!\n"); } =} - + reaction(dTimer) -> d, s, m {= if (self->ststState) { self->dState = (self->dState + 1) % 100; @@ -110,49 +105,49 @@ reactor GetUserInput { } =} - physical action user_input:char; - output stst:int; - + physical action user_input: char + output stst: int + reaction(startup) -> user_input {= // Start the thread that listens for Enter or Return. pthread_t thread_id; - pthread_create(&thread_id, NULL, &read_input, user_input); + pthread_create(&thread_id, NULL, &read_input, user_input); =} - + reaction(user_input) -> stst {= if (user_input->value == 'x') { lf_request_stop(); } else { - lf_set(stst, 42); + lf_set(stst, 42); } =} } reactor PrintOutput { - input m:int; - input s:int; - input d:int; - + input m: int + input s: int + input d: int + reaction(m) {= printf("m = %d, ", m->value); =} - + reaction(s) {= printf("s = %d, ", s->value); =} - + reaction(d) {= printf("d = %d\n", d->value); =} - } +} main reactor Chrono { - in = new GetUserInput(); - chrono = new ChronoLogic(); - out = new PrintOutput(); - - in.stst -> chrono.stst; - chrono.m -> out.m; - chrono.s -> out.s; - chrono.d -> out.d; + in = new GetUserInput() + chrono = new ChronoLogic() + out = new PrintOutput() + + in.stst -> chrono.stst + chrono.m -> out.m + chrono.s -> out.s + chrono.d -> out.d } diff --git a/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg.lf b/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg.lf index b38b1f91..d459c67c 100644 --- a/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg.lf +++ b/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg.lf @@ -1,27 +1,25 @@ /** - * Ptolemy II Example for Modes. - * An SDF model where the ModalModel requires more than one token - * on its input in order to fire. - * http://ptolemy.org/systems/models/modal/ModalSDF/index.html - * + * Ptolemy II Example for Modes. An SDF model where the ModalModel requires more than one token on + * its input in order to fire. http://ptolemy.org/systems/models/modal/ModalSDF/index.html + * * @author Alexander Schulz-Rosengarten */ -target Python{ - threads: 0 -}; +target Python { + single-threaded: true +} + +reactor Sinewave(sample_rate = 125 usec, frequency=440, phase=0) { + output data -reactor Sinewave(sample_rate(125 usec), frequency(440), phase(0)) { - output data; - - timer rate(0, sample_rate); - - state ramp({=itertools.count(0)=}) + timer rate(0, sample_rate) + + state ramp = {= itertools.count(0) =} preamble {= import math import itertools =} - + reaction(rate) -> data {= x = next(self.ramp) * ((self.frequency * 2 * self.math.pi) * (self.sample_rate / SEC(1))) + self.phase #print("Sinewave (%f, %f)" % (x, self.math.sin(x))) @@ -29,91 +27,87 @@ reactor Sinewave(sample_rate(125 usec), frequency(440), phase(0)) { =} } -reactor ModalModel(sample_size(10)) { - input data; - output out; - - state sample({=[None] * sample_size=}); - state count(0); - - state _mode(0); # Only present w/o mode support - - // These actions only mimic the Ptolemy structure - logical action processAVG +reactor ModalModel(sample_size=10) { + input data + output out + + state sample = {= [None] * sample_size =} + state count = 0 + + state _mode = 0 # Only present w/o mode support + + logical action processAVG # These actions only mimic the Ptolemy structure logical action processMAX - - -// initial mode AVG { + # initial mode AVG { /** @label Mode AVG: Collect */ reaction(data) -> processAVG {= - if self._mode == 0: # Only present w/o mode support + if self._mode == 0: # Only present w/o mode support self.sample[self.count] = data.value self.count += 1 if self.count == self.sample_size: self.count = 0 processAVG.lf_schedule(0) =} - + /** @label Mode AVG: Process and Transition */ reaction(processAVG) -> out {= - if self._mode == 0: # Only present w/o mode support + if self._mode == 0: # Only present w/o mode support #print("Processing: ", self.sample) out.set(sum(self.sample) / self.sample_size) # Transition to MAX self._mode = 1 # set_mode(MAX) =} - -// } -// mode MAX { - + + # } + # mode MAX { /** @label Mode MAX: Collect */ reaction(data) -> processMAX {= - if self._mode == 1: # Only present w/o mode support + if self._mode == 1: # Only present w/o mode support self.sample[self.count] = data.value self.count += 1 if self.count == self.sample_size: self.count = 0 processMAX.lf_schedule(0) =} - + /** @label Mode MAX: Process and Transition */ + # } reaction(processMAX) -> out {= - if self._mode == 1: # Only present w/o mode support + if self._mode == 1: # Only present w/o mode support #print("Processing: ", self.sample) out.set(max(self.sample)) # Transition to MAX self._mode = 0 # set_mode(AVG) =} -// } } reactor Plotter { - input data; - - state plot_data({=[]=}) - + input data + + state plot_data = {= [] =} + preamble {= import matplotlib.pyplot as plt =} - + reaction(startup) {= self.plt.ion() self.plt.title("Plot") - - # No idea why multiple draws are required but it works this way + + # No idea why multiple draws are required but it works this way self.plt.draw() self.plt.pause(0.00000001) self.plt.draw() self.plt.pause(0.00000001) =} - + reaction(data) {= #print("Plotting ", data.value) self.plot_data.append(data.value) self.plt.stem(self.plot_data) - - # No idea why multiple draws are required but it works this way + + # No idea why multiple draws are required but it works this way self.plt.draw() self.plt.pause(0.00000001) self.plt.draw() @@ -122,7 +116,7 @@ reactor Plotter { } main reactor { - s = new Sinewave(sample_rate = 125 msec, frequency = 0.44) + s = new Sinewave(sample_rate = 125 msec, frequency=0.44) m = new ModalModel() p = new Plotter() s.data -> m.data diff --git a/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg_v2.lf b/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg_v2.lf index 99a71528..44459387 100644 --- a/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg_v2.lf +++ b/experiments/C/src/ModalModels/Motivation/SineAvgMax/sine_max_avg_v2.lf @@ -1,64 +1,60 @@ /** - * Ptolemy II Example for Modes. - * An SDF model where the ModalModel requires more than one token - * on its input in order to fire. - * http://ptolemy.org/systems/models/modal/ModalSDF/index.html - * + * Ptolemy II Example for Modes. An SDF model where the ModalModel requires more than one token on + * its input in order to fire. http://ptolemy.org/systems/models/modal/ModalSDF/index.html + * * @author Alexander Schulz-Rosengarten */ -target Python{ - threads: 0 -}; +target Python { + single-threaded: false +} import Plotter, Sinewave from "./sine_max_avg.lf" -reactor ModalModel(sample_size(10)) { - input data; - output out; - - state sample({=[None] * sample_size=}); - state count(0); - - state _mode(0); # Only present w/o mode support - +reactor ModalModel(sample_size=10) { + input data + output out + + state sample = {= [None] * sample_size =} + state count = 0 + + state _mode = 0 # Only present w/o mode support + logical action process - + reaction(data) -> process {= - if self._mode == 0: # Only present w/o mode support + if self._mode == 0: # Only present w/o mode support self.sample[self.count] = data.value self.count += 1 if self.count == self.sample_size: self.count = 0 process.lf_schedule(0) =} - -// initial mode AVG { + # initial mode AVG { /** @label Mode AVG: Process and Transition */ reaction(process) -> out {= - if self._mode == 0: # Only present w/o mode support + if self._mode == 0: # Only present w/o mode support print("Processing: ", self.sample) out.set(sum(self.sample) / self.sample_size) # Transition to MAX self._mode = 1 # set_mode(MAX) =} - -// } -// mode MAX { - + + # } + # mode MAX { /** @label Mode MAX: Process and Transition */ + # } reaction(process) -> out {= - if self._mode == 1: # Only present w/o mode support + if self._mode == 1: # Only present w/o mode support print("Processing: ", self.sample) out.set(max(self.sample)) # Transition to MAX self._mode = 0 # set_mode(AVG) =} -// } } main reactor { - s = new Sinewave(sample_rate = 125 msec, frequency = 0.44) + s = new Sinewave(sample_rate = 125 msec, frequency=0.44) m = new ModalModel() p = new Plotter() s.data -> m.data diff --git a/experiments/C/src/ModalModels/ReflexGame/ModalReflexGame.lf b/experiments/C/src/ModalModels/ReflexGame/ModalReflexGame.lf index 2e7d7be3..0cd37abd 100644 --- a/experiments/C/src/ModalModels/ReflexGame/ModalReflexGame.lf +++ b/experiments/C/src/ModalModels/ReflexGame/ModalReflexGame.lf @@ -1,72 +1,72 @@ /** - * This example illustrates the use of logical and physical actions, - * asynchronous external inputs, the use of startup and shutdown - * reactions, and the use of actions with values. - * - * The example is fashioned after an Esterel implementation given by - * Berry and Gonthier in "The ESTEREL synchronous programming language: - * design, semantics, implementation," Science of Computer Programming, - * 19(2) pp. 87-152, Nov. 1992, DOI: 10.1016/0167-6423(92)90005-V. - * + * This example illustrates the use of logical and physical actions, asynchronous external inputs, + * the use of startup and shutdown reactions, and the use of actions with values. + * + * The example is fashioned after an Esterel implementation given by Berry and Gonthier in "The + * ESTEREL synchronous programming language: design, semantics, implementation," Science of Computer + * Programming, 19(2) pp. 87-152, Nov. 1992, DOI: 10.1016/0167-6423(92)90005-V. + * * Version that uses modes! - * + * * @author Edward A. Lee * @author Marten Lohstroh * @author Alexander Schulz-Rosengarten */ target C { keepalive: true -}; +} /** - * Produce a counting sequence at random times with a minimum - * and maximum time between outputs specified as parameters. - * + * Produce a counting sequence at random times with a minimum and maximum time between outputs + * specified as parameters. + * * @param min_time The minimum time between outputs. * @param max_time The maximum time between outputs. */ -reactor RandomSource(min_time:time(2 sec), max_time:time(8 sec)) { +reactor RandomSource(min_time: time = 2 sec, max_time: time = 8 sec) { preamble {= - // Generate a random additional delay over the minimum. + // Generate a random additional delay over the minimum. // Assume millisecond precision is enough. interval_t additional_time(interval_t min_time, interval_t max_time) { int interval_in_msec = (max_time - min_time) / MSEC(1); return (rand() % interval_in_msec) * MSEC(1); } =} - input another:int; - output out:int; - logical action prompt(min_time); - state count:int(0); - + input another: int + output out: int + logical action prompt(min_time) + state count: int = 0 + reaction(startup) -> prompt {= printf("***********************************************\n"); printf("Watch for the prompt, then hit Return or Enter.\n"); printf("Type Control-D (EOF) to quit.\n\n"); - + // Random number functions are part of stdlib.h, which is included by reactor.h. // Set a seed for random number generation based on the current time. srand(time(0)); - + // Schedule the first event. lf_schedule(prompt, additional_time(0, self->max_time - self->min_time)); =} + reaction(prompt) -> out {= self->count++; printf("%d. Hit Return or Enter!", self->count); fflush(stdout); lf_set(out, self->count); =} + reaction(another) -> prompt {= // Schedule the next event. lf_schedule(prompt, additional_time(0, self->max_time - self->min_time)); =} } + /** - * Upon receiving a prompt, record the time of the prompt, - * then listen for user input. When the user hits return, - * then lf_schedule a physical action that records the time - * of this event and then report the response time. + * Upon receiving a prompt, record the time of the prompt, then listen for user input. When the user + * hits return, then lf_schedule a physical action that records the time of this event and then + * report the response time. */ reactor GetUserInput { preamble {= @@ -85,34 +85,35 @@ reactor GetUserInput { } =} - physical action user_response:char; - state prompt_time:time(0); - state total_time_in_ms:int(0); - state count:int(0); - - input prompt:int; - output another:int; - + physical action user_response: char + state prompt_time: time = 0 + state total_time_in_ms: int = 0 + state count: int = 0 + + input prompt: int + output another: int + reaction(startup) -> user_response {= // Start the thread that listens for Enter or Return. lf_thread_t thread_id; - lf_thread_create(&thread_id, &read_input, user_response); + lf_thread_create(&thread_id, &read_input, user_response); =} - + initial mode Preparing { - reaction(prompt) -> Waiting {= + reaction(prompt) -> reset(Waiting) {= self->prompt_time = lf_time_logical(); // Switch to mode waiting for user input lf_set_mode(Waiting); =} - + reaction(user_response) {= printf("YOU CHEATED!\n"); lf_request_stop(); =} } + mode Waiting { - reaction(user_response) -> another, Preparing {= + reaction(user_response) -> another, reset(Preparing) {= int time_in_ms = (lf_time_logical() - self->prompt_time) / 1000000LL; printf("Response time in milliseconds: %d\n", time_in_ms); self->count++; @@ -125,14 +126,14 @@ reactor GetUserInput { lf_set_mode(Preparing); =} } - + reaction(user_response) {= if (user_response->value == EOF) { lf_request_stop(); return; } =} - + reaction(shutdown) {= if (self->count > 0) { printf("\n**** Average response time: %d.\n", self->total_time_in_ms/self->count); @@ -141,9 +142,10 @@ reactor GetUserInput { } =} } + main reactor { - p = new RandomSource(); - g = new GetUserInput(); - p.out -> g.prompt; - g.another -> p.another; + p = new RandomSource() + g = new GetUserInput() + p.out -> g.prompt + g.another -> p.another } diff --git a/experiments/C/src/Mutations/ScatterGather.lf b/experiments/C/src/Mutations/ScatterGather.lf index d78b228e..4ce544fe 100644 --- a/experiments/C/src/Mutations/ScatterGather.lf +++ b/experiments/C/src/Mutations/ScatterGather.lf @@ -1,24 +1,23 @@ /** - * This program is a concept demonstration showing how higher-order - * combinators could be defined in the C target. This example has - * a ScatterGather reactor that, upon receiving an input array, - * creates as many instances of a Worker reactor as there are elements - * in the array, distributes the elements of the array to those workers, - * and puts onto the reaction queue all reactions in the workers that are - * sensitive to those inputs. When the workers are finished executing, - * a second reaction in ScatterGather will execute. That second reaction - * collects all the results of the workers into an array and sends the - * result to the output. - * - * This program should not be used as a model for creating designs. - * It uses internal, implementation-specific details that will eventually - * be abstracted out into an API. + * This program is a concept demonstration showing how higher-order combinators could be defined in + * the C target. This example has a ScatterGather reactor that, upon receiving an input array, + * creates as many instances of a Worker reactor as there are elements in the array, distributes the + * elements of the array to those workers, and puts onto the reaction queue all reactions in the + * workers that are sensitive to those inputs. When the workers are finished executing, a second + * reaction in ScatterGather will execute. That second reaction collects all the results of the + * workers into an array and sends the result to the output. + * + * This program should not be used as a model for creating designs. It uses internal, + * implementation-specific details that will eventually be abstracted out into an API. */ -target C { - threads: 4 -}; +target C { + single-threaded: false, + workers: 4 +} + reactor Source { - output out:int[]; + output out: int[] + reaction(startup) -> out {= SET_NEW_ARRAY(out, 8); for (int i=0; i < 8; i++) { @@ -26,37 +25,42 @@ reactor Source { } =} } -reactor Worker(id:int(2)) { - input worker_in:int; - output worker_out:int; + +reactor Worker(id: int = 2) { + input worker_in: int + output worker_out: int + reaction(worker_in) -> worker_out {= printf("Worker received in first reaction%d\n", worker_in->value); lf_set(worker_out, worker_in->value * self->id); =} + reaction(worker_in) {= printf("Worker received second %d\n", worker_in->value); =} } + reactor ScatterGather2 { - input in:int[]; - output out:int[]; - + input in: int[] + output out: int[] + // Create a state variable to pass the workers created in // the first reaction to the second reaction. This is an array // of pointers to the self struct of the worker. // The type of a self struct is the reactor class name, converted to // lower case, followed by _self_t. - state workers:worker_self_t**({=NULL=}); - + state workers: worker_self_t** = {= NULL =} + // The data type of the upstream source does not match the // data type of the workers' inputs, so we have to create an // array of places to store the input data. - state worker_inputs:worker_worker_in_t*({=NULL=}); - + state worker_inputs: worker_worker_in_t* = {= NULL =} + // Create a template worker, which ensures that downstream // levels are correct. The template worker could have any // number of reactions sensitive to the input we will provide. - template_worker = new Worker(); + template_worker = new Worker() + reaction(in) -> template_worker.worker_in {= lf_set(template_worker.worker_in, in->value[0]); // SCATTER(in, worker_in, Worker, self->workers, id); @@ -93,6 +97,7 @@ reactor ScatterGather2 { pthread_cond_broadcast(&reaction_q_changed); pthread_mutex_unlock(&mutex); =} + reaction(in, template_worker.worker_out) -> out {= SET_NEW_ARRAY(out, in->length); // FIXME: We should be checking template_worker.worker_out_is_present. @@ -106,8 +111,10 @@ reactor ScatterGather2 { // they have a destructor. =} } + reactor Print { - input in:int[]; + input in: int[] + reaction(in) {= printf("["); for(int i = 0; i < in->length; i++) { @@ -119,10 +126,11 @@ reactor Print { printf("]\n"); =} } + main reactor { - s = new Source(); - g = new ScatterGather2(); - p = new Print(); - s.out -> g.in; - g.out -> p.in; + s = new Source() + g = new ScatterGather2() + p = new Print() + s.out -> g.in + g.out -> p.in } diff --git a/experiments/C/src/Mutations/SieveOfEratosthenes.lf b/experiments/C/src/Mutations/SieveOfEratosthenes.lf index 32305ba1..09c0ccc8 100644 --- a/experiments/C/src/Mutations/SieveOfEratosthenes.lf +++ b/experiments/C/src/Mutations/SieveOfEratosthenes.lf @@ -1,39 +1,37 @@ /** - * This program is a concept demonstration showing how higher-order - * combinators could be defined in the C target. This example has - * an Sieve reactor that, upon receiving an integer input, feeds - * it through a growing chain of Filter reactors, each of which filters - * out from the stream multiples of one prime number. When a number - * emerges from the end of the chain, it is prime. This number triggers - * a reaction in the Sieve reactor that forwards the prime to its output - * and then splices in a new Filter reactor to filter out multiples of - * this primte. The Sieve starts with one Filter which filters out - * multiples of 2. - * - * This program should not be used as a model for creating designs. - * It uses internal, implementation-specific details that will eventually - * be abstracted out into an API. - * + * This program is a concept demonstration showing how higher-order combinators could be defined in + * the C target. This example has an Sieve reactor that, upon receiving an integer input, feeds it + * through a growing chain of Filter reactors, each of which filters out from the stream multiples + * of one prime number. When a number emerges from the end of the chain, it is prime. This number + * triggers a reaction in the Sieve reactor that forwards the prime to its output and then splices + * in a new Filter reactor to filter out multiples of this primte. The Sieve starts with one Filter + * which filters out multiples of 2. + * + * This program should not be used as a model for creating designs. It uses internal, + * implementation-specific details that will eventually be abstracted out into an API. + * * @author Edward A. Lee */ target C { fast: true } + reactor Count { - timer t(0, 1 sec); - output out:int; - // Start at 3 because 1 and 2 are taken care of. - state c:int(3); + timer t(0, 1 sec) + output out: int + state c: int = 3 // Start at 3 because 1 and 2 are taken care of. + reaction(t) -> out {= lf_set(out, self->c); // printf("Count sent %d\n", self->c); self->c++; =} } -reactor Filter(prime:int(2)) { - input in:int; - output out:int; - + +reactor Filter(prime: int = 2) { + input in: int + output out: int + reaction(in) -> out {= // printf("Filter for prime %d received %d\n", self->prime, in->value); if (in->value % self->prime != 0) { @@ -42,20 +40,20 @@ reactor Filter(prime:int(2)) { } =} } + reactor Sieve { - input in:int; - output out:int; - - // Create the first filter. - filter2 = new Filter(prime = 2); - in -> filter2.in; - + input in: int + output out: int + + filter2 = new Filter(prime=2) // Create the first filter. + in -> filter2.in + // React to a new prime number. reaction(filter2.out) -> out {= // Forward the new prime number. // printf("Sieve received prime: %d\n", filter2.out->value); lf_set(out, filter2.out->value); - + // Splice in a new Filter. filter_self_t* filter = new_Filter(); filter->prime = filter2.out->value; @@ -66,33 +64,33 @@ reactor Sieve { filter->_lf__reaction_0.triggered_sizes = (int*)calloc(1, sizeof(int)); filter->_lf__reaction_0.output_produced = (bool**)calloc(1, sizeof(bool*)); filter->_lf__reaction_0.output_produced[0] = &filter->_lf_out.is_present; - + // Get a pointer to the self struct of the last reactor in the chain. assert(self->_lf__reaction_0.last_enabling_reaction == &last->_lf__reaction_0); filter_self_t *last = (filter_self_t*)self->_lf__reaction_0.last_enabling_reaction->self; - + // Adjust last_enabling_reaction for optimized execution of a chain. self->_lf__reaction_0.last_enabling_reaction = &filter->_lf__reaction_0; filter->_lf__reaction_0.last_enabling_reaction = &last->_lf__reaction_0; - + filter->_lf_out.num_destinations = 1; - + filter->_lf__reaction_0.triggered_sizes[0] = 1; trigger_t** trigger_array = (trigger_t**)calloc(1, sizeof(trigger_t*)); filter->_lf__reaction_0.triggers[0] = trigger_array; - + // Inherit destination triggers from the last filter in the chain. filter->_lf__reaction_0.triggers[0][0] = last->_lf__reaction_0.triggers[0][0]; - + // Update the triggers of the last reactor to point to the new filter... last->_lf__reaction_0.triggers[0][0] = &filter->_lf__in; - + // The input data of the new filter comes from the output of the last filter. filter->_lf_in = (filter_in_t*)&last->_lf_out; - + // The container's reaction gets its data from the new filter from now on. self->_lf_filter2.out = (filter_out_t*)&filter->_lf_out; - + // Unfortunately, need to reallocate the arrays that indicate the is_present // fields because now there are two more. // FIXME: This should be done in large chunks rather than on each call? @@ -102,7 +100,7 @@ reactor Sieve { lf_request_stop(); } else { _lf_is_present_fields = new_memory; - + new_memory = (bool**)realloc(_lf_is_present_fields_abbreviated, (_lf_is_present_fields_size + 1) * sizeof(bool*)); if (new_memory == NULL) { lf_print_error("Out of memory!"); @@ -111,27 +109,30 @@ reactor Sieve { _lf_is_present_fields_abbreviated = new_memory; } _lf_is_present_fields_size += 1; - + _lf_is_present_fields[_lf_is_present_fields_size - 1] = &filter->_lf_out.is_present; - + // NOTE: The level of the reaction in the newly created reactor should be set // and downstream reactions incremented, but levels have no effect on this system. // In fact, the reaction queue never has more than one item on it. } =} } -reactor Print(stop_after:int(10000)) { - input in:int; - state count:int(1); + +reactor Print(stop_after: int = 10000) { + input in: int + state count: int = 1 + reaction(in) {= printf("Prime %d: %d\n", self->count++, in->value); if (self->count > self->stop_after) lf_request_stop(); =} } + main reactor { - c = new Count(); - s = new Sieve(); - p = new Print(); - c.out -> s.in; - s.out -> p.in; + c = new Count() + s = new Sieve() + p = new Print() + c.out -> s.in + s.out -> p.in } diff --git a/experiments/C/src/PowerTrain/PowerTrain.lf b/experiments/C/src/PowerTrain/PowerTrain.lf index ea6cc96d..bd2b4858 100644 --- a/experiments/C/src/PowerTrain/PowerTrain.lf +++ b/experiments/C/src/PowerTrain/PowerTrain.lf @@ -1,15 +1,17 @@ -target C {threads: 1, keepalive: true, flags: "-lncurses"}; +target C { + single-threaded: true, + keepalive: true, + compiler-flags: "-lncurses" +} /** - * FIXME: there are still two problems with this implementation: - * 1. Use of a global variable to let the ncurses thread find the actions - * that it should lf_schedule. It would be helpful to have some kind of generic - * framework for setting up interactive tests. - * 2. When the program runs out of space on the terminal, newlines are ignored. - * It is not clear to me why this is the case, but I've only just now started - * using ncurses, which is a pretty rich toolkit. More on ncurses here: + * FIXME: there are still two problems with this implementation: 1. Use of a global variable to let + * the ncurses thread find the actions that it should lf_schedule. It would be helpful to have some + * kind of generic framework for setting up interactive tests. 2. When the program runs out of space + * on the terminal, newlines are ignored. It is not clear to me why this is the case, but I've only + * just now started using ncurses, which is a pretty rich toolkit. More on ncurses here: * http://web.mit.edu/barnowl/src/ncurses/ncurses-5.4/doc/html/ncurses-intro.html - * + * * @author Marten Lohstroh */ preamble {= @@ -41,7 +43,7 @@ preamble {= char c = 0; // Command: [1-6|q-y] int v = 0; while (true) { - bool skip = false; // Whether to lf_schedule and event or not + bool skip = false; // Whether to lf_schedule and event or not c = getch(); switch(c) { case 'q': @@ -66,11 +68,11 @@ preamble {= skip = true; break; } - + if (!skip) { lf_schedule_int(pedals.brake, 0, v); } - + skip = false; switch(c) { case '1': @@ -95,7 +97,7 @@ preamble {= skip = true; break; } - + if (!skip) { lf_schedule_int(pedals.accelerate, 0, v); } @@ -114,41 +116,40 @@ preamble {= =} reactor MotorControl { - - input brkOn:bool; - input angle:int; - output torque:int; - state braking:bool(true); - + input brkOn: bool + input angle: int + output torque: int + state braking: bool = true + //@label Set torque to zero if car is braking reaction(brkOn) -> torque {= self->braking = brkOn->value; lf_set(torque, 0); =} - + //@label Adjust torque unless car is braking - reaction(angle) -> torque {= + reaction(angle) -> torque {= if (!self->braking) { - lf_set(torque, calc_motor_torque(angle->value)); - } else if (angle->value > 0){ - printw("Cannot accelerate; release brake pedal first.\n"); - } + lf_set(torque, calc_motor_torque(angle->value)); + } else if (angle->value > 0){ + printw("Cannot accelerate; release brake pedal first.\n"); + } =} } -reactor BrakePedal { - output angle:int; - output applied:bool; - - physical action a(0, 1 msec, "replace"):int; - state last:int(1); - +reactor BrakePedal { + output angle: int + output applied: bool + + physical action a(0, 1 msec, "replace"): int + state last: int = 1 + // @label Setup callback - reaction(startup) -> a {= + reaction(startup) -> a {= pedals.brake = a; init_sensors(); =} - + // @label Output reported angle reaction(a) -> angle, applied {= if (self->last != a->value) { @@ -157,87 +158,81 @@ reactor BrakePedal { } else if (a->value == 0) { lf_set(applied, false); // nonzero to zero } - + self->last = a->value; - - lf_set(angle, a->value); + + lf_set(angle, a->value); } - =} + =} } reactor Accelerator { - - state pedal:int(-1); - physical action a(0, 2 msec, "replace"):int; - output angle:int; - state last:int(0); - + state pedal: int = -1 + physical action a(0, 2 msec, "replace"): int + output angle: int + state last: int = 0 + // @label Setup callback reaction(startup) -> a {= pedals.accelerate = a; init_sensors(); =} - + // @label Output reported angle reaction(a) -> angle {= if (self->last != a->value) { lf_set(angle, a->value); self->last = a->value; } - =} - + =} } // @label Apply the requested force reactor Brakes { - input force:int; - + input force: int + // @label Reaction with deadline reaction(force) {= printw("Adjusting brake power to %dN; on time!\n", force->value); - =} deadline (2 msec) {= + =} deadline(2 msec) {= printw("Adjusting brake power to %dN; too late!\n", force->value); =} } // @label Apply the requested torque reactor Motor { - input torque:int; - + input torque: int + // @label Reaction with deadline reaction(torque) {= printw("Adjusting engine torque to %dNm; on time!\n", torque->value); - =} deadline (3 msec) {= + =} deadline(3 msec) {= printw("Adjusting engine torque to %dNm; too late!\n", torque->value); =} } // @label Adjust the force reactor BrakeControl { - input angle:int; - output force:int; - + input angle: int + output force: int + reaction(angle) -> force {= lf_set(force, calc_brake_force(angle->value)); =} } -/** - * Reactor that implements a simplified power train control module. - */ +/** Reactor that implements a simplified power train control module. */ main reactor PowerTrain { - - bp = new BrakePedal(); - a = new Accelerator(); - bc = new BrakeControl(); - mc = new MotorControl(); - b = new Brakes(); - m = new Motor(); - - bp.angle -> bc.angle; - bc.force -> b.force; - bp.applied -> mc.brkOn; - mc.torque -> m.torque; - a.angle -> mc.angle; - + bp = new BrakePedal() + a = new Accelerator() + bc = new BrakeControl() + mc = new MotorControl() + b = new Brakes() + m = new Motor() + + bp.angle -> bc.angle + bc.force -> b.force + bp.applied -> mc.brkOn + mc.torque -> m.torque + a.angle -> mc.angle } diff --git a/experiments/C/src/RecursiveInstantiation/SieveOfErastothenes.lf b/experiments/C/src/RecursiveInstantiation/SieveOfErastothenes.lf index 12cfb0f6..800c7004 100644 --- a/experiments/C/src/RecursiveInstantiation/SieveOfErastothenes.lf +++ b/experiments/C/src/RecursiveInstantiation/SieveOfErastothenes.lf @@ -4,61 +4,62 @@ * not compile because recursive reactor instantiation is forbidden. * @author Peter Donovan */ -target C { fast: true, Build-Type: Debug } + target C { fast: true, Build-Type: Debug } -reactor Count(start: int(2)) { - timer t(0, 1 sec); - output out: int; - state c: int(start); - reaction(t) -> out {= - lf_set(out, self->c++); - =} -} - -reactor Filter { - input n_in: int - output n_out: int - output result: int - input initialize: int - - reset state p: int(0) - - initial mode End { - reaction(n_in) -> result, reset(Filter) {= - lf_set(result, n_in->value); - lf_set_mode(Filter); - assert(self->p == 0); // This reaction can only be triggered once - self->p = n_in->value; - =} - } - - mode Filter { - end = new Filter() - - end.result -> result - - reaction(n_in) -> end.n_in {= - if (n_in->value % self->p != 0) { - lf_set(end.n_in, n_in->value); - } - =} - } -} - -reactor Print(stop_after:int(10000)) { - input in:int; - state count:int(1); - reaction(in) {= - printf("Prime %d: %d\n", self->count++, in->value); - if (self->count > self->stop_after) lf_request_stop(); - =} -} - -main reactor SieveOfErastothenes { - count = new Count() - filter = new Filter() - print = new Print(stop_after=10) - - count.out -> filter.n_in - filter.result -> print.in -} + reactor Count(start: int=2) { + timer t(0, 1 sec); + output out: int; + state c: int=start; + reaction(t) -> out {= + lf_set(out, self->c++); + =} + } + + reactor Filter { + input n_in: int + output n_out: int + output result: int + input initialize: int + + reset state p: int=0 + + initial mode End { + reaction(n_in) -> result, reset(Filter) {= + lf_set(result, n_in->value); + lf_set_mode(Filter); + assert(self->p == 0); // This reaction can only be triggered once + self->p = n_in->value; + =} + } + + mode Filter { + end = new Filter() + + end.result -> result + + reaction(n_in) -> end.n_in {= + if (n_in->value % self->p != 0) { + lf_set(end.n_in, n_in->value); + } + =} + } + } + + reactor Print(stop_after:int=10000) { + input in:int; + state count:int=1; + reaction(in) {= + printf("Prime %d: %d\n", self->count++, in->value); + if (self->count > self->stop_after) lf_request_stop(); + =} + } + + main reactor SieveOfErastothenes { + count = new Count() + filter = new Filter() + print = new Print(stop_after=10) + + count.out -> filter.n_in + filter.result -> print.in + } + \ No newline at end of file diff --git a/experiments/C/src/RecursiveInstantiation/SieveOfErastothenesUnrolled.lf b/experiments/C/src/RecursiveInstantiation/SieveOfErastothenesUnrolled.lf index 5634306d..cb34a153 100644 --- a/experiments/C/src/RecursiveInstantiation/SieveOfErastothenesUnrolled.lf +++ b/experiments/C/src/RecursiveInstantiation/SieveOfErastothenesUnrolled.lf @@ -1,16 +1,19 @@ /** - * This is an expanded version of SieveOfErastothenes.lf. It duplicates - * code to prevent recursive instantiation, which is currently forbidden. - * However, it would be better to implement this using recursive - * instantiation, as attempted in SieveOfErastothenes.lf. + * This is an expanded version of SieveOfErastothenes.lf. It duplicates code to prevent recursive + * instantiation, which is currently forbidden. However, it would be better to implement this using + * recursive instantiation, as attempted in SieveOfErastothenes.lf. * @author Peter Donovan */ -target C { fast: true, Build-Type: Debug } +target C { + fast: true, + Build-Type: Debug +} + +reactor Count(start: int = 2) { + timer t(0, 1 sec) + output out: int + state c: int = start -reactor Count(start: int(2)) { - timer t(0, 1 sec); - output out: int; - state c: int(start); reaction(t) -> out {= lf_set(out, self->c++); =} @@ -22,7 +25,7 @@ reactor Filter0 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -37,7 +40,6 @@ reactor Filter0 { end = new Filter1() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -52,7 +54,7 @@ reactor Filter1 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -67,7 +69,6 @@ reactor Filter1 { end = new Filter2() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -82,7 +83,7 @@ reactor Filter2 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -97,7 +98,6 @@ reactor Filter2 { end = new Filter3() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -112,7 +112,7 @@ reactor Filter3 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -127,7 +127,6 @@ reactor Filter3 { end = new Filter4() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -142,7 +141,7 @@ reactor Filter4 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -157,7 +156,6 @@ reactor Filter4 { end = new Filter5() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -172,7 +170,7 @@ reactor Filter5 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -187,7 +185,6 @@ reactor Filter5 { end = new Filter6() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -202,7 +199,7 @@ reactor Filter6 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -217,7 +214,6 @@ reactor Filter6 { end = new Filter7() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -232,7 +228,7 @@ reactor Filter7 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -247,7 +243,6 @@ reactor Filter7 { end = new Filter8() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -262,7 +257,7 @@ reactor Filter8 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -277,7 +272,6 @@ reactor Filter8 { end = new Filter9() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -292,7 +286,7 @@ reactor Filter9 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -307,7 +301,6 @@ reactor Filter9 { end = new Filter10() end.result -> result - reaction(n_in) -> end.n_in {= if (n_in->value % self->p != 0) { lf_set(end.n_in, n_in->value); @@ -322,7 +315,7 @@ reactor Filter10 { output result: int input initialize: int - reset state p: int(0) + reset state p: int = 0 initial mode End { reaction(n_in) -> result, reset(Filter) {= @@ -333,22 +326,21 @@ reactor Filter10 { =} } + // end = new Filter11() + // end.result -> result + // reaction(n_in) -> end.n_in {= + // if (n_in->value % self->p != 0) { + // lf_set(end.n_in, n_in->value); + // } + // =} mode Filter { - // end = new Filter11() - - // end.result -> result - - // reaction(n_in) -> end.n_in {= - // if (n_in->value % self->p != 0) { - // lf_set(end.n_in, n_in->value); - // } - // =} } } -reactor Print(stop_after:int(10000)) { - input in:int; - state count:int(1); +reactor Print(stop_after: int = 10000) { + input in: int + state count: int = 1 + reaction(in) {= printf("Prime %d: %d\n", self->count++, in->value); if (self->count > self->stop_after) lf_request_stop(); diff --git a/experiments/C/src/Safety/Cutter.lf b/experiments/C/src/Safety/Cutter.lf index 7e49e8e3..0b1d55ab 100644 --- a/experiments/C/src/Safety/Cutter.lf +++ b/experiments/C/src/Safety/Cutter.lf @@ -1,23 +1,19 @@ /** * This Lingua Franca (LF) program simulates a machine controller with two buttons - * - an emergency stop button and a run button. The machine can operate in two - * modes: Off and Running. When the run button is pressed, the machine enters the - * Running mode and prints a tick every 100 milliseconds. If the emergency stop - * button is pressed, the machine immediately goes back to the Off mode. If a - * deadline is violated in the Running mode, the program prints a deadline - * violation message and switches back to the Off mode. Furthermore, a sensor - * simulator is started at the beginning of the program, and pressing the 'x' key - * will stop the program. This LF program shows a rudimentary example of a - * safety-critical system where safety operations (like an emergency stop) have - * priority and time-sensitive operations are monitored for deadline violations. + * - an emergency stop button and a run button. The machine can operate in two modes: Off and + * Running. When the run button is pressed, the machine enters the Running mode and prints a tick + * every 100 milliseconds. If the emergency stop button is pressed, the machine immediately goes + * back to the Off mode. If a deadline is violated in the Running mode, the program prints a + * deadline violation message and switches back to the Off mode. Furthermore, a sensor simulator is + * started at the beginning of the program, and pressing the 'x' key will stop the program. This LF + * program shows a rudimentary example of a safety-critical system where safety operations (like an + * emergency stop) have priority and time-sensitive operations are monitored for deadline + * violations. */ target C { workers: 2, keepalive: true, - files: [ - "/lib/c/reactor-c/util/sensor_simulator.c", - "/lib/c/reactor-c/util/sensor_simulator.h" - ], + files: ["/lib/c/reactor-c/util/sensor_simulator.c", "/lib/c/reactor-c/util/sensor_simulator.h"], cmake-include: ["/lib/c/reactor-c/util/sensor_simulator.cmake"], build-type: RelWithDebInfo // Release with debug info } @@ -42,7 +38,9 @@ reactor ButtonController(key: char = '\0', description: string = "Button") { } =} - reaction(pushed) -> push {= lf_set(push, true); =} + reaction(pushed) -> push {= + lf_set(push, true); + =} } reactor MachineController { @@ -50,7 +48,9 @@ reactor MachineController { input run: bool initial mode Off { - reaction(run) -> reset(Running) {= lf_set_mode(Running); =} + reaction(run) -> reset(Running) {= + lf_set_mode(Running); + =} } mode Running { @@ -64,15 +64,17 @@ reactor MachineController { // With centralized coordination, time cannot advance to trigger this // reaction unless null messages have arrived on emergencyStop. - reaction(t) -> reset(Off) {= show_tick("*"); =} deadline(10 msec) {= + reaction(t) -> reset(Off) {= + show_tick("*"); + =} deadline(10 msec) {= lf_set_mode(Off); =} } } main reactor { - eStop = new ButtonController(key = 'e', description = "Emergency stop") - run = new ButtonController(key = 'r', description = "Run") + eStop = new ButtonController(key='e', description = "Emergency stop") + run = new ButtonController(key='r', description="Run") m = new MachineController() eStop.push -> m.emergencyStop @@ -87,5 +89,7 @@ main reactor { lf_print("Exit with 'x'"); =} - reaction(stop) {= lf_request_stop(); =} + reaction(stop) {= + lf_request_stop(); + =} } diff --git a/experiments/C/src/SolarUAV/SolarUAV.lf b/experiments/C/src/SolarUAV/SolarUAV.lf index ab8fc3ab..47762f98 100644 --- a/experiments/C/src/SolarUAV/SolarUAV.lf +++ b/experiments/C/src/SolarUAV/SolarUAV.lf @@ -1,57 +1,48 @@ /** - * This example illustrates the high level design of a Solar powered Unmanned - * Aerial Vehicle (Solar UAV). - * The LF program is meant for timing analysis and identfying the critical paths. - - * The Solar UAV main mission is to take pictures of an area, stitch them to - * reconstruct a map, perform image processing (when possible), and then send - * the result to the base station. - * + * This example illustrates the high level design of a Solar powered Unmanned Aerial Vehicle (Solar + * UAV). The LF program is meant for timing analysis and identfying the critical paths. + * + * The Solar UAV main mission is to take pictures of an area, stitch them to reconstruct a map, + * perform image processing (when possible), and then send the result to the base station. + * * There are 3 main paths in the LF program: - * * Flight control path: Its goal is to maintain the UAV stable and moving, - so it does not crash. It is hard real-time and it includes the following - reactors: IMUSensor, GPSSensor, KalmanFilter, Controller and Actuator. - * * Mission critical path: This path identifies the reactors that are realtive - to the mission success, which is covering the entire target zone. To this - end, the Planner will compute where to go, based on target zone and the - coverage. This is hard real-time as well, and the reactors involved are: - IMUSensor, GPSSensor, Gimbal, Camera, BatterySensor, ComIn, ImageStitching, - Planner, Controller, and Actuator. - * * Non mission critical path: Processing the stitched images should only happen - whenever there are empty time slots and enough power. Reactors in this - path are: ImageProcessing and ComOut and this is soft real-time. - FIXME: Reactions in this path can/need to be preambtable. LF semantics, - however, say that reactions are atomic... - * + * * Flight control path: Its goal is to maintain the UAV stable and moving, so it does not crash. + * It is hard real-time and it includes the following reactors: IMUSensor, GPSSensor, KalmanFilter, + * Controller and Actuator. + * * Mission critical path: This path identifies the reactors that are realtive to the mission + * success, which is covering the entire target zone. To this end, the Planner will compute where to + * go, based on target zone and the coverage. This is hard real-time as well, and the reactors + * involved are: IMUSensor, GPSSensor, Gimbal, Camera, BatterySensor, ComIn, ImageStitching, + * Planner, Controller, and Actuator. + * * Non mission critical path: Processing the stitched images should only happen whenever there are + * empty time slots and enough power. Reactors in this path are: ImageProcessing and ComOut and this + * is soft real-time. FIXME: Reactions in this path can/need to be preambtable. LF semantics, + * however, say that reactions are atomic... + * * @author Mirco Theile * @author Chadlia Jerad * @author Shaokai Lin * @author Erling R. Jellum */ - target C { - keepalive: true - // FIXME: add the structures. Currently everything is either int or void. -}; - -/** - * Control related reactors - */ + keepalive: true // FIXME: add the structures. Currently everything is either int or void. +} +/** Control related reactors */ // Sensing reactors -reactor IMUSensor (period:time(20ms)){ +reactor IMUSensor(period: time = 20 ms) { timer t(0, period) - output imuOutput: int // struct 9 values double (64bits) + output imuOutput: int // struct 9 values double (64bits) reaction(t) -> imuOutput {= lf_set(imuOutput, 5); =} } -reactor GPSSensor (period:time(50ms)){ +reactor GPSSensor(period: time = 50 ms) { timer t(0, period) - output gpsOutput : int - + output gpsOutput: int + reaction(t) -> gpsOutput {= // =} @@ -61,25 +52,25 @@ reactor GPSSensor (period:time(50ms)){ reactor KalmanFilter { input imuInput: int input gpsInput: int - output inertialData: int // Filtred sensor data everything + output inertialData: int // Filtred sensor data everything reaction(imuInput, gpsInput) -> inertialData {= // lf_set(inertialData, 5); =} } -// The Planner will periodically call an AI agent to process the controlTarget. +// The Planner will periodically call an AI agent to process the controlTarget. reactor Planner { input targetZoneIn: int input inertialData: int - input coverageIn : void - input weatherForecast : int - input batteryState : int + input coverageIn: void + input weatherForecast: int + input batteryState: int output controlTraget: int - timer t(0, 100ms) + timer t(0, 100 ms) state targetZone: int state coverage: void - state stateValues: void // All remaining stuff + state stateValues: void // All remaining stuff reaction(inertialData) {= // Update state stateValues @@ -99,15 +90,14 @@ reactor Planner { reaction(coverageIn) {= // Update state coverage - =} deadline (20ms) {= + =} deadline(20 ms) {= lf_print("help coverage"); =} reaction(t) -> controlTraget {= - // This is the main task: compute where to go based on target zone - // and coverage + // This is the main task: compute where to go based on target zone + // and coverage =} - } // Controller @@ -115,84 +105,78 @@ reactor Controller { input inertialData: int input controlTraget: int output actuation: int - state previousTarget : int + state previousTarget: int - reaction(inertialData) -> actuation {= - =} + reaction(inertialData) -> actuation {= =} - reaction(controlTraget) {= - // Just updates the + reaction(controlTraget) {= + // Just updates the previousTarget = controlTraget; - =} deadline (100ms) {= + =} deadline(100 ms) {= lf_print("help"); =} } reactor Actuator { - input actuation : int - reaction(actuation) {= - =} + input actuation: int + + reaction(actuation) {= =} } -/** - * Camera actuation control - */ -reactor Gimbal{ +/** Camera actuation control */ +reactor Gimbal { input imuData: int logical action gimbalDone - + reaction(imuData) -> gimbalDone {= lf_schedule(gimbalDone, 0); =} - - reaction (gimbalDone) {==} deadline (10ms) {= - // + + reaction(gimbalDone) {= =} deadline(10 ms) {= + // =} } -/** - * Communication modules with the base station - */ +/** Communication modules with the base station */ reactor ComIn { output targetZone: int output weatherForecast: int - timer t(0, 50ms); + timer t(0, 50 ms) + reaction(t) -> targetZone {= // polling commands form base station - // and then updating the wayPoints + // and then updating the wayPoints =} } reactor ComOut { input inertialData: int input result: int - + reaction(inertialData, result) {= // sendiong packets =} } -/** - * Mission - */ +/** Mission */ reactor Camera { - timer t(20msecs, 20msecs) + timer t(20 msecs, 20 msecs) output frame: void - reaction (t) -> frame {= + reaction(t) -> frame {= frame.set(); // send a "frame" =} } reactor ImageStitching { input inertialData: int - input frame : void + input frame: void output coverage: void - output stitchedImageChunk : void + output stitchedImageChunk: void state stitchedImage: void - state latestInertialData : int - timer t(0, 1s) + state latestInertialData: int + timer t(0, 1 s) reaction(frame) -> coverage {= // Do batch processing and only set the output @@ -200,19 +184,19 @@ reactor ImageStitching { =} reaction(t) -> stitchedImageChunk {= - // + // =} + reaction(inertialData) {= latestInertialData = inertialData; =} - } reactor ImageProcessing { input stitchedImageChunk: void input batteryState: int input weatherForecast: int - output result : int + output result: int state processedImage: void reaction(stitchedImageChunk) -> result {= @@ -221,27 +205,21 @@ reactor ImageProcessing { =} } -/** - * Battery management - */ +/** Battery management */ reactor BatterySensor { - output batteryState : int - timer t(0, 100ms) - reaction(t) -> batteryState {= + output batteryState: int + timer t(0, 100 ms) - =} + reaction(t) -> batteryState {= =} } -/** - * Main reactor - */ +/** Main reactor */ main reactor { - // Instantiations - imuSensor = new IMUSensor() + imuSensor = new IMUSensor() // Instantiations gpsSensor = new GPSSensor() kalmanFilter = new KalmanFilter() planner = new Planner() - controller = new Controller() + controller = new Controller() actuator = new Actuator() gimbal = new Gimbal() comIn = new ComIn() @@ -251,8 +229,7 @@ main reactor { imageProcessing = new ImageProcessing() batterySensor = new BatterySensor() - // Connections - imuSensor.imuOutput -> kalmanFilter.imuInput + imuSensor.imuOutput -> kalmanFilter.imuInput // Connections gpsSensor.gpsOutput -> kalmanFilter.gpsInput kalmanFilter.inertialData -> planner.inertialData kalmanFilter.inertialData -> controller.inertialData diff --git a/experiments/C/src/SpatAnalysis/MQTTPublisher.lf b/experiments/C/src/SpatAnalysis/MQTTPublisher.lf index f42a1b7e..8ec5c123 100644 --- a/experiments/C/src/SpatAnalysis/MQTTPublisher.lf +++ b/experiments/C/src/SpatAnalysis/MQTTPublisher.lf @@ -1,63 +1,56 @@ /** - * Reactor that publishes strings to a specified MQTT topic. - * See MQTTPhysical and MQTTLogical for prerequisites and an example usage. - * + * Reactor that publishes strings to a specified MQTT topic. See MQTTPhysical and MQTTLogical for + * prerequisites and an example usage. + * * @author Ravi Akella * @author Edward A. Lee */ -target C; +target C /** - * Reactor that publishes strings to a specified MQTT topic. - * This publisher appends to the end of the message the - * timestamp of the message at the publisher. The receiving - * end adds to this timestamp a specified offset, and if that - * offset is larger than the current physical time at which it - * receives the message, assigns that incremented timestamp - * to the message. If the offset is always greater than or - * equal to the transport latency plus the clock synchronization - * error, then the overall program remains deterministic. - * - * This publisher also ensures in-order delivery messages to - * subscribers. If an attempt is made to send a message before - * the delivery of the previous message has completed, then the reaction - * that sends the message (the reaction to an input 'in') will - * block until the previous delivery has completed. + * Reactor that publishes strings to a specified MQTT topic. This publisher appends to the end of + * the message the timestamp of the message at the publisher. The receiving end adds to this + * timestamp a specified offset, and if that offset is larger than the current physical time at + * which it receives the message, assigns that incremented timestamp to the message. If the offset + * is always greater than or equal to the transport latency plus the clock synchronization error, + * then the overall program remains deterministic. + * + * This publisher also ensures in-order delivery messages to subscribers. If an attempt is made to + * send a message before the delivery of the previous message has completed, then the reaction that + * sends the message (the reaction to an input 'in') will block until the previous delivery has + * completed. * * @param topic The topic name on which to publish. * @param address The IP address of the MQTT broker. - * @param clientID The name of the client instance. - * These names are required to be unique. - * @param include_physical_timestamp If true, include - * in the message the time of the local physical clock - * when the message is sent. + * @param clientID The name of the client instance. These names are required to be unique. + * @param include_physical_timestamp If true, include in the message the time of the local physical + * clock when the message is sent. * @see MQTTSubscriber. */ -reactor MQTTPublisher ( - topic:string("DefaultTopic"), - address:string("tcp://localhost:1883"), - clientID:string("DefaultPublisher"), - include_physical_timestamp:int(0) -) { +reactor MQTTPublisher( + topic: string = "DefaultTopic", + address: string = "tcp://localhost:1883", + clientID: string = "DefaultPublisher", + include_physical_timestamp: int = 0) { preamble {= #include "MQTTClient.h" #include "core/util.h" - + // Timeout for completion of message sending in milliseconds. #define TIMEOUT 10000L - + // Connection options for the client. // Making this global means that all instances of this reactor have // the same connection options. MQTTClient_connectOptions pub_connect_options = MQTTClient_connectOptions_initializer; - + // Struct type used to keep track of messages in flight between reactions. typedef struct inflight_t { bool message_in_flight; MQTTClient_deliveryToken delivery_token; char* message; } inflight_t; - + // Callback invoked once delivery is complete. void pub_delivered(void *inflight, MQTTClient_deliveryToken dt) { // printf("DEBUG: Message with token value %d delivery confirmed\n", dt); @@ -71,22 +64,22 @@ reactor MQTTPublisher ( } =} /** - * Input type char* instead of string is used for dynamically - * allocated character arrays (as opposed to static constant strings). + * Input type char* instead of string is used for dynamically allocated character arrays (as + * opposed to static constant strings). */ - input in:char*; - + input in: char* + /** State variable that keeps track of a message in flight. */ - state inflight:inflight_t({={false, 0, NULL}=}); - + state inflight: inflight_t0 = {= {false, 0, NULL} =} + /** The client object. */ - state client:MQTTClient({=NULL=}); - + state client: MQTTClient = {= NULL =} + /** The message object. */ - state mqtt_msg:MQTTClient_message({=MQTTClient_message_initializer=}); - + state mqtt_msg: MQTTClient_message = {= MQTTClient_message_initializer =} + /** Connect to the broker. Exit if this fails. */ - reaction(startup){= + reaction(startup) {= MQTTClient_create(&self->client, self->address, self->clientID, MQTTCLIENT_PERSISTENCE_NONE, NULL); pub_connect_options.keepAliveInterval = 20; pub_connect_options.cleansession = 1; @@ -98,7 +91,7 @@ reactor MQTTPublisher ( // Second argument is a pointer to context that will be passed to pub_delivered, // which in this case is a pointer to the inflight state variable. MQTTClient_setCallbacks(self->client, &self->inflight, pub_connection_lost, NULL, pub_delivered); - + // Connect to the broker. int rc; // response code. if ((rc = MQTTClient_connect(self->client, &pub_connect_options)) != MQTTCLIENT_SUCCESS) { @@ -108,14 +101,13 @@ reactor MQTTPublisher ( } // printf("DEBUG: MQTTPublisher connected to broker.\n"); =} - + /** - * React to an input by sending a message with the value of the input as the payload. - * If delivery has not yet completed for a previously sent message, then wait for - * it to complete before proceeding (blocking this reaction). - * This copies the message from the input into a buffer, so the input can - * freed upon return of this reaction (LF will automatically decrement its - * reference count). + * React to an input by sending a message with the value of the input as the payload. If delivery + * has not yet completed for a previously sent message, then wait for it to complete before + * proceeding (blocking this reaction). This copies the message from the input into a buffer, so + * the input can freed upon return of this reaction (LF will automatically decrement its reference + * count). */ reaction(in) {= if(self->inflight.message_in_flight) { @@ -130,7 +122,7 @@ reactor MQTTPublisher ( } //printf("DEBUG: Publishing message: %s\n", in->value); // printf("DEBUG: on topic '%s' for publisher with ClientID: %s\n", self->topic, self->clientID); - + // Allocate memory for a copy of the message. // The length includes the null-terminator of the string and 8 bytes for the timestamp. int length = strlen(in->value) + 1 + sizeof(instant_t); @@ -139,25 +131,25 @@ reactor MQTTPublisher ( } self->inflight.message = malloc(sizeof(char) * length); memcpy(self->inflight.message, in->value, strlen(in->value) + 1); - + // Append the current timestamp to the message. // This is always last, after the physical timestamp if it is included. - encode_ll(lf_time_logical(), + encode_ll(lf_time_logical(), (unsigned char*)(self->inflight.message + length - sizeof(instant_t)) ); // printf("DEBUG: Timestamp of sending message: %lld.\n", *timestamp); self->mqtt_msg.payload = self->inflight.message; self->mqtt_msg.payloadlen = length; - + // QoS 2 means that the message will be delivered exactly once. self->mqtt_msg.qos = 2; - + // Retained messages are held by the server and sent to future new subscribers. // Specify that this message should not be retained. // It will be sent only to subscribers currently subscribed. self->mqtt_msg.retained = 0; - + // As close as possible to the publishing of the message, insert // the physical timestamp if it has been requested. if (self->include_physical_timestamp) { @@ -168,12 +160,12 @@ reactor MQTTPublisher ( //For Dashboard, echo physical time stamp else { printf("EVENT: sender_pts: %lld\n", lf_time_physical()); - } - + } + MQTTClient_publishMessage(self->client, self->topic, &self->mqtt_msg, &self->inflight.delivery_token); - self->inflight.message_in_flight = true; + self->inflight.message_in_flight = true; =} - + /** Disconnect the client. */ reaction(shutdown) {= printf("MQTTPublisher: Client ID %s disconnecting.\n", self->clientID); diff --git a/experiments/C/src/SpatAnalysis/MQTTSubscriber.lf b/experiments/C/src/SpatAnalysis/MQTTSubscriber.lf index 9de8435f..1c20952b 100644 --- a/experiments/C/src/SpatAnalysis/MQTTSubscriber.lf +++ b/experiments/C/src/SpatAnalysis/MQTTSubscriber.lf @@ -1,85 +1,68 @@ /** - * Reactor that subscribes to a specified MQTT topic on which - * string messages are published. - * See MQTTPhysical and MQTTLogical for prerequisites and an example usage. - * + * Reactor that subscribes to a specified MQTT topic on which string messages are published. See + * MQTTPhysical and MQTTLogical for prerequisites and an example usage. + * * @author Ravi Akella * @author Edward A. Lee */ -target C; +target C /** - * Reactor that subscribes to a specified MQTT topic on which - * string messages are published. - * This reactor extracts the sender's timestamp from the message - * and adds to this timestamp the specified offset. If that - * offset is larger than the current physical time at which this - * reactor receives the message, then it assigns that incremented timestamp - * to the message. Otherwise, the received message gets a timestamp - * equal to the physical time at which the message is received. - * - * In addition, if the corresponding MQTTPublisher has its - * parameter include_physical_timestamp set to non-zero, then - * this reactor measures the physical transport time from the - * time of sending (as reported by the physical clock at the sender) - * and the time at which this reactor's reaction is invoked - * (as reported by the physical clock at this receiver). - * - * If the offset is always greater than or equal to the sum of - * transport latency, clock synchronization error, and execution - * times between creation of the timestamped event at the sender - * and its reception at the receiver, then the overall program - * remains deterministic. - * Hence, a good practice, if the application can tolerate the - * latency, is to set the offset to be larger than this expected - * communication latency between a publisher and a subscriber. - * To determine whether an execution was deterministic in this - * sense, set include_physical_timestamp = 0 at the sender, - * set a non-zero offset at the receiver, and see if the reported - * average latency equals the reported maximum latency. If they - * are equal, then execution was deterministic. - * - * By default, the offset is 0, so the assigned timestamp will - * always be the physical time at which the message is received, - * assuming the clock synchronization error is less than the - * transport latency. With this setting, this timestamp increment - * also measures the total communication latency. If the sender has - * include_physical_timestamp set to zero, then this reactor - * reports at the end of execution the average and - * maximum timestamp increments rather than physical latencies. - * + * Reactor that subscribes to a specified MQTT topic on which string messages are published. This + * reactor extracts the sender's timestamp from the message and adds to this timestamp the specified + * offset. If that offset is larger than the current physical time at which this reactor receives + * the message, then it assigns that incremented timestamp to the message. Otherwise, the received + * message gets a timestamp equal to the physical time at which the message is received. + * + * In addition, if the corresponding MQTTPublisher has its parameter include_physical_timestamp set + * to non-zero, then this reactor measures the physical transport time from the time of sending (as + * reported by the physical clock at the sender) and the time at which this reactor's reaction is + * invoked (as reported by the physical clock at this receiver). + * + * If the offset is always greater than or equal to the sum of transport latency, clock + * synchronization error, and execution times between creation of the timestamped event at the + * sender and its reception at the receiver, then the overall program remains deterministic. Hence, + * a good practice, if the application can tolerate the latency, is to set the offset to be larger + * than this expected communication latency between a publisher and a subscriber. To determine + * whether an execution was deterministic in this sense, set include_physical_timestamp = 0 at the + * sender, set a non-zero offset at the receiver, and see if the reported average latency equals the + * reported maximum latency. If they are equal, then execution was deterministic. + * + * By default, the offset is 0, so the assigned timestamp will always be the physical time at which + * the message is received, assuming the clock synchronization error is less than the transport + * latency. With this setting, this timestamp increment also measures the total communication + * latency. If the sender has include_physical_timestamp set to zero, then this reactor reports at + * the end of execution the average and maximum timestamp increments rather than physical latencies. + * * @param topic The topic name on which to publish. * @param address The IP address of the MQTT broker. - * @param clientID The name of the client instance. - * These names are required to be unique. + * @param clientID The name of the client instance. These names are required to be unique. * @param offset The offset to add to the sender's timestamp. * @see MQTTPublisher. */ - -reactor MQTTSubscriber ( - address:string("tcp://localhost:1883"), - clientID:string("DefaultSubscriber"), - topic:string("DefaultTopic"), - offset:time(0 msec) -) { +reactor MQTTSubscriber( + address: string = "tcp://localhost:1883", + clientID: string = "DefaultSubscriber", + topic: string = "DefaultTopic", + offset: time = 0 msec) { preamble {= #include "MQTTClient.h" #include "core/util.h" #include - + #define QOS 2 #define TIMEOUT 10000L - + // Connection options for the client. // Making this global means that all instances of this reactor have // the same connection options. MQTTClient_connectOptions sub_connect_options = MQTTClient_connectOptions_initializer; - + // Callback function invoked by MQTT when a message arrives. int message_arrived( - void *incoming_message, - char *topicName, - int topicLen, + void *incoming_message, + char *topicName, + int topicLen, MQTTClient_message *message ) { instant_t receive_physical_time = lf_time_physical(); @@ -89,9 +72,9 @@ reactor MQTTSubscriber ( instant_t* physical_timestamp = (instant_t*)((char*)message->payload + string_length + 1); // printf("DEBUG: MQTTReceiver.message_arrived: Received message after measured latency of %lld nsec (assuming synchronized clocks).\n", receive_physical_time - *physical_timestamp); } - + // printf("DEBUG: MQTTSubscriber: Message arrived on topic %s: %s\n", topicName, (char*)message->payload); - + // Extract the timestamp and calculate delay from current_time to that timestamp. // Note that if this subscriber's current time is ahead of current time // at the publisher (something that should not happen even in a distributed @@ -100,15 +83,15 @@ reactor MQTTSubscriber ( // by printing a warning to stderr and revise the timestamp to current time. // First acquire the mutex lock. Otherwise, logical time could have a big // jump between this calculation and the call to lf_schedule, resulting in a long delay. - + // NOTE: Since lf_schedule_copy also acquires this lock, we assume here that the // pthreads library correctly implements recursive mutex locks to unlock all // locks held by the current thread when waiting for signals. pthread_mutex_lock(&mutex); - + instant_t timestamp = extract_ll((unsigned char*)message->payload + message->payloadlen - sizeof(instant_t)); interval_t delay = timestamp - lf_time_logical(); - + // Schedule the event. Since incoming_message is a physical action, // the offset specified in the second argument will be added to current_time // and to the min_delay in the action and then compared to physical time. @@ -125,74 +108,63 @@ reactor MQTTSubscriber ( // We include that in the copy so that the reaction to the physical action // can measure the latency. lf_schedule_copy(incoming_message, delay, (char*)message->payload, message->payloadlen); - + pthread_mutex_unlock(&mutex); // MQTTClient_freeMessage() also frees the memory allocated to the payload, // which is why we have to copy the message here. MQTTClient_freeMessage(&message); MQTTClient_free(topicName); - + // Return true to indicate that the message has been successfully handled. return 1; } - + /** Callback invoked if the connection is lost. */ void sub_connection_lost(void *incoming_message, char *cause) { printf("\nConnection lost\n"); printf(" cause: %s\n", cause); } =} - - /** - * Output for sending the incoming MQTT message. - * Use type char* rather than string because it is not - * a static string, but rather dynamically allocated memory. - */ - output message:char*; /** - * Action that is triggered when there is an incoming MQTT message. - * Use a physical action here so that the logical time when the action - * is triggered is the physical time of arrival of the message if - * the offset is 0. If the offset is larger than the communication - * latency plus the clock synchronization error, - * then the incoming message will have have a timestamp deterministically - * larger than the sender's timestamp by the offset. - */ - physical action incoming_message(offset):char*; - - /** - * State variable storing the MQTT client created for each instance of this reactor. - */ - state client:MQTTClient({=NULL=}); - - /** - * Maximum observed latency from the originator of the message to here. - */ - state max_latency:time(0); - - /** - * Sum of all observed latencies. + * Output for sending the incoming MQTT message. Use type char* rather than string because it is + * not a static string, but rather dynamically allocated memory. */ - state latencies:time(0); - + output message: char* + /** - * Count of messages. + * Action that is triggered when there is an incoming MQTT message. Use a physical action here so + * that the logical time when the action is triggered is the physical time of arrival of the + * message if the offset is 0. If the offset is larger than the communication latency plus the + * clock synchronization error, then the incoming message will have have a timestamp + * deterministically larger than the sender's timestamp by the offset. */ - state count:int(0); - + physical action incoming_message(offset): char* + + /** State variable storing the MQTT client created for each instance of this reactor. */ + state client: MQTTClient = {= NULL =} + + /** Maximum observed latency from the originator of the message to here. */ + state max_latency: time = 0 + + /** Sum of all observed latencies. */ + state latencies: time = 0 + + /** Count of messages. */ + state count: int = 0 + reaction(startup) -> incoming_message {= MQTTClient_create(&self->client, self->address, self->clientID, MQTTCLIENT_PERSISTENCE_NONE, NULL); sub_connect_options.keepAliveInterval = 20; sub_connect_options.cleansession = 1; - + // Set up callback functions. // Last argument should be a pointer to a function to // handle notification of delivery of a sent message. // But this reactor isn't sending any messages. MQTTClient_setCallbacks(self->client, incoming_message, sub_connection_lost, message_arrived, NULL); - + // Connect to the broker. int rc; // response code. if ((rc = MQTTClient_connect(self->client, &sub_connect_options)) != MQTTCLIENT_SUCCESS) { @@ -200,24 +172,24 @@ reactor MQTTSubscriber ( fprintf(stderr, "Perhaps one is not running? Return code: %d\n", rc); exit(EXIT_FAILURE); } - + MQTTClient_subscribe(self->client, self->topic, QOS); =} - + reaction(incoming_message) -> message {= self->count++; - + // The incoming_message action contains a token that we can just forward. // The allocated memory will be freed when the token's reference count hits 0. // Note that this token will still contain the sender's timestamp. lf_set_token(message, incoming_message->token); - + // Get the sender's timestamp. instant_t* timestamp = (instant_t*)( incoming_message->token->value + incoming_message->token->length - sizeof(instant_t) ); //printf("DEBUG: Received message carrying timestamp %lld.\n", self->count, *timestamp); - + // If the offset is 0, then the latency will be a measure of // the physical latency between creation of the message at the sender // and its receipt here, offset by the clock synchronization error, @@ -230,7 +202,7 @@ reactor MQTTSubscriber ( printf("EVENT: sender_lts: %lld\n", *timestamp); printf("EVENT: receiver_lts: %lld\n", logical_timestamp); printf("EVENT: logical_latency: %lld\n", latency); - + // If a physical timestamp was sent, use that to collect // latency stats instead of the logical time increment. size_t string_length = strlen(incoming_message->value); // Assumes null-terminated string. @@ -246,14 +218,14 @@ reactor MQTTSubscriber ( else{ //physical timestamp was not sent, echo physical timestamp for dashboard printf("EVENT: receiver_pts: %lld\n", lf_time_physical()); } - + self->latencies += latency; - + if (latency > self->max_latency) { self->max_latency = latency; } =} - + reaction(shutdown) {= printf("MQTTSubscriber: Maximum latency measured at receiver (in nsec): %lld.\n", self->max_latency); if (self->count > 0) { diff --git a/experiments/C/src/SpatAnalysis/influxWrite.lf b/experiments/C/src/SpatAnalysis/influxWrite.lf index f345845d..e70c794e 100644 --- a/experiments/C/src/SpatAnalysis/influxWrite.lf +++ b/experiments/C/src/SpatAnalysis/influxWrite.lf @@ -1,34 +1,31 @@ /** - * This Lingua Franca (LF) program is designed to interact with an InfluxDB - * database and process event trace data. Upon startup, the program checks if the - * specified database exists and creates it if necessary. Then, it reads a trace - * file line by line, processing and extracting event information. The program - * writes the processed data as points to the InfluxDB database. Upon shutdown, the - * program queries the database for the last 10 records, sorted by descending - * timestamp, and prints the results to the console. This LF program demonstrates - * how to work with time-series data and interact with InfluxDB using the Influx - * package in TypeScript. + * This Lingua Franca (LF) program is designed to interact with an InfluxDB database and process + * event trace data. Upon startup, the program checks if the specified database exists and creates + * it if necessary. Then, it reads a trace file line by line, processing and extracting event + * information. The program writes the processed data as points to the InfluxDB database. Upon + * shutdown, the program queries the database for the last 10 records, sorted by descending + * timestamp, and prints the results to the console. This LF program demonstrates how to work with + * time-series data and interact with InfluxDB using the Influx package in TypeScript. */ -target TypeScript; +target TypeScript -/* - * Uses another package @ https://github.com/node-influx/node-influx/tree/master/examples/express_response_times +/** + * Uses another package @ + * https://github.com/node-influx/node-influx/tree/master/examples/express_response_times */ - main reactor { - - preamble{= + preamble {= // @ts-ignore import * as Influx from "influx"; import * as os from "os"; import * as fs from "fs"; import * as path from "path"; - + const databaseName = "LF_EVENTS"; const measurementName = "event_times"; const traceFilePath = "../../my_trace"; - + const influx = new Influx.InfluxDB({ host: "localhost", @@ -53,8 +50,10 @@ main reactor { ], }); =} - - reaction(startup){= + + timer t(0, 0 msec) + + reaction(startup) {= influx .getDatabaseNames() // @ts-ignore @@ -72,21 +71,17 @@ main reactor { console.error(`Error creating Influx database!`); }); =} - - timer t(0, 0 msec); - + reaction(t) {= - - let fieldRecord: {[key:string] : number} = {}; - + var lines; try { - + const data = fs.readFileSync(path.resolve(__dirname, traceFilePath), "utf8") lines = data.split(/\r?\n/); - - + + lines.forEach((line) => { if(line.indexOf("EVENT") < 0){ return; @@ -118,8 +113,8 @@ main reactor { console.error(err) } =} - - reaction(shutdown){= + + reaction(shutdown) {= influx .query( ` diff --git a/experiments/C/src/SpatAnalysis/spatReceiver.lf b/experiments/C/src/SpatAnalysis/spatReceiver.lf index 1b2415fd..2cd4f0a6 100644 --- a/experiments/C/src/SpatAnalysis/spatReceiver.lf +++ b/experiments/C/src/SpatAnalysis/spatReceiver.lf @@ -1,22 +1,18 @@ -/* - * Example PTIDES application for connected vehicle application - * Demo Tile: RSU Coordinated Speed alignment - * This is the vehicle side application that - * - Requests RSU side application to provide speed recommendations based on its SPAT - * - Uses MQTT pub/sub +/** + * Example PTIDES application for connected vehicle application Demo Tile: RSU Coordinated Speed + * alignment This is the vehicle side application that + * - Requests RSU side application to provide speed recommendations based on its SPAT + * - Uses MQTT pub/sub * @author Ravi Akella */ - target C { - threads: 1, // Must use threaded implementation so lf_schedule is thread safe. - flags: "-I/usr/local/include -L/usr/local/lib -g -lpaho-mqtt3c src-gen/core/util.c ", + single-threaded: true, // Must use single-threaded implementation so lf_schedule is thread safe. + compiler-flags: "-I/usr/local/include -L/usr/local/lib -g -lpaho-mqtt3c src-gen/core/util.c ", timeout: 60 secs, keepalive: true -}; - - -import MQTTSubscriber from "MQTTSubscriber.lf"; +} +import MQTTSubscriber from "MQTTSubscriber.lf" /** * Reactor that prints an incoming string. @@ -25,50 +21,50 @@ import MQTTSubscriber from "MQTTSubscriber.lf"; */ reactor recvSpatMessage { preamble {= - #define BILLION 1000000000LL - typedef struct spat_status{ - int signal_phase; //integer code for signal 1-Red, 2-Green, 3-Yellow - long long unsigned residual; // remaining time for phase change - } spat_status; + #define BILLION 1000000000LL + typedef struct spat_status{ + int signal_phase; //integer code for signal 1-Red, 2-Green, 3-Yellow + long long unsigned residual; // remaining time for phase change + } spat_status; =} - - input message:spat_status*; + + input message: spat_status* + reaction(message) {= - printf("Received Phase=\"%d\", residual=%llu.\n", + printf("Received Phase=\"%d\", residual=%llu.\n", message->value->signal_phase, message->value->residual ); =} } - reactor recvSpatMessage2 { - input message:char*; - state signal_phase:int(0); - state residual:time(0 nsec); - + input message: char* + state signal_phase: int = 0 + state residual: time = 0 nsec + reaction(message) {= /*printf("PrintMessage: At (elapsed) time %lld, subscriber receives: %s\n", lf_time_logical_elapsed(), message->value );*/ - + sscanf(message->value, "%d %llu", &self->signal_phase, &self->residual); printf("EVENT: residual: %llu\n", self->residual); printf("EVENT: phase: %d\n", self->signal_phase); printf("spatReceiver: At phy time %lld, Phase:%d residual:%llu nsec.\n", lf_time_physical(), self->signal_phase, self->residual); - =} deadline (10 msec) {= + =} deadline(10 msec) {= printf("EVENT: deadline_miss: true\n"); =} } - /** * Reactor that prints an incoming string. * @param prefix A prefix for the message. * @input message The message. */ reactor PrintMessage { - input message:char*; + input message: char* + reaction(message) {= printf("PrintMessage: At (elapsed) time %lld, subscriber receives: %s\n", lf_time_logical_elapsed(), @@ -78,16 +74,12 @@ reactor PrintMessage { } main reactor spatReceiver { - - rsuMsg = new recvSpatMessage2(); + rsuMsg = new recvSpatMessage2() sub = new MQTTSubscriber( - //address = "tcp://host.docker.internal:1883", - address = "tcp://localhost:1883", - clientID = "Vehicle 760", - topic = "spat/rsu101", - offset = 10 msec - ); - - - sub.message->rsuMsg.message; + address="tcp://localhost:1883", // address = "tcp://host.docker.internal:1883", + clientID = "Vehicle 760", + topic="spat/rsu101", + offset = 10 msec) + + sub.message -> rsuMsg.message } diff --git a/experiments/C/src/SpatAnalysis/spatRecommender.lf b/experiments/C/src/SpatAnalysis/spatRecommender.lf index 3d3901ef..feff9a40 100644 --- a/experiments/C/src/SpatAnalysis/spatRecommender.lf +++ b/experiments/C/src/SpatAnalysis/spatRecommender.lf @@ -1,78 +1,75 @@ -/* - * Example PTIDES application for connected vehicle application - * Demo Tile: RSU Coordinated Speed alignment - * This is the Road side unit application that - * - Accepts requests to provide speed recommendations (based on its SPAT) from vehicles - * - Uses MQTT pub/sub +/** + * Example PTIDES application for connected vehicle application Demo Tile: RSU Coordinated Speed + * alignment This is the Road side unit application that + * - Accepts requests to provide speed recommendations (based on its SPAT) from vehicles + * - Uses MQTT pub/sub * @author Ravi Akella */ - target C { - - threads: 1, // Must use threaded implementation so lf_schedule is thread safe. - flags: "-I/usr/local/include -L/usr/local/lib -g -lpaho-mqtt3c src-gen/core/util.c", + single-threaded: true, // Must use single-threaded implementation so lf_schedule is thread safe. + compiler-flags: "-I/usr/local/include -L/usr/local/lib -g -lpaho-mqtt3c src-gen/core/util.c", timeout: 60 secs, keepalive: true -}; - -import MQTTPublisher from "MQTTPublisher.lf"; +} +import MQTTPublisher from "MQTTPublisher.lf" -reactor phaseChanger{ - +reactor phaseChanger { preamble {= - #define BILLION 1000000000LL + #define BILLION 1000000000LL + typedef struct spat_status{ int signal_phase; //integer code for signal 1-Red, 2-Green, 3-Yellow long long unsigned residual; // remaining time for phase change } spat_status; - - =} - - input in:int; - output out:char*; - output outStruct:spat_status; - - state status:spat_status(1, 0); //(signal_phase, residual time)) - - //Internal state variables - state phase_start_time:time(0 msec); - state phase_duration:time(0 msec); - - logical action red:int; - logical action green:int; - logical action yellow:int; - + =} + + input in: int + output out: char* + output outStruct: spat_status + + state status: spat_status = {= {1, 0} =} //(signal_phase, residual time)) + + state phase_start_time: time = 0 msec // Internal state variables + state phase_duration: time = 0 msec + + logical action red: int + logical action green: int + logical action yellow: int + + timer t(0, 500 msec) + reaction(startup) -> red {= lf_schedule_int(red, MSEC(100), 1); self->phase_start_time = lf_time_logical(); - =} - - reaction(red) -> green, out, outStruct {= + =} + + reaction(red) -> green, out, outStruct {= self->status.signal_phase = red->value; - self->phase_duration = MSEC(5000); - self->phase_start_time = lf_time_logical(); - - self->status.residual = self->phase_duration; - int length = snprintf(NULL, 0, "%d %llu", self->status.signal_phase, self->status.residual) + 1; - // Dynamically allocate memory for the output. - SET_NEW_ARRAY(out, length); - // Populate the output string and increment the count. - snprintf(out->value, length, "%d %llu", self->status.signal_phase, self->status.residual); - printf("spatRecommender: At phy time %lld, phase change message: %s\n", - lf_time_physical(), - out->value - ); - - - lf_set(outStruct, self->status); - lf_schedule_int(green, self->phase_duration, 2); + self->phase_duration = MSEC(5000); + self->phase_start_time = lf_time_logical(); + + self->status.residual = self->phase_duration; + int length = snprintf(NULL, 0, "%d %llu", self->status.signal_phase, self->status.residual) + 1; + // Dynamically allocate memory for the output. + SET_NEW_ARRAY(out, length); + // Populate the output string and increment the count. + snprintf(out->value, length, "%d %llu", self->status.signal_phase, self->status.residual); + printf("spatRecommender: At phy time %lld, phase change message: %s\n", + lf_time_physical(), + out->value + ); + + + lf_set(outStruct, self->status); + lf_schedule_int(green, self->phase_duration, 2); =} + reaction(green) -> yellow, out {= self->status.signal_phase = green->value; self->phase_duration = MSEC(3000); self->phase_start_time = lf_time_logical(); - + self->status.residual = self->phase_duration; int length = snprintf(NULL, 0, "%d %llu", self->status.signal_phase, self->status.residual) + 1; // Dynamically allocate memory for the output. @@ -83,11 +80,11 @@ reactor phaseChanger{ lf_time_physical(), out->value ); - + lf_schedule_int(yellow, self->phase_duration, 3); =} - - reaction(yellow)->red, out {= + + reaction(yellow) -> red, out {= self->status.signal_phase = yellow->value; self->phase_duration = MSEC(1000); self->phase_start_time = lf_time_logical(); @@ -102,13 +99,11 @@ reactor phaseChanger{ lf_time_physical(), out->value ); - + lf_schedule_int(red, self->phase_duration, 1); =} - - timer t(0, 500 msec); - reaction(t)->out{= + reaction(t) -> out {= self->status.residual = self->phase_duration - lf_time_logical() + self->phase_start_time; int length = snprintf(NULL, 0, "%d %llu", self->status.signal_phase, self->status.residual) + 1; // Dynamically allocate memory for the output. @@ -128,11 +123,12 @@ reactor phaseChanger{ * @input message The message. */ reactor recvSpatMessage { - input message:char*; + input message: char* + reaction(message) {= printf("Received (phase, residual in nsec) %s:\n", message->value); - - /*printf("Received Phase=\"%d\", residual=%llu.\n", + + /*printf("Received Phase=\"%d\", residual=%llu.\n", message->signal_phase, message->residual ); */ @@ -141,25 +137,21 @@ reactor recvSpatMessage { // expected parameter is for testing. reactor Print { - input in:char*; - + input in: char* + reaction(in) {= printf("Received: name = %d, value = %lld\n", in->value.signal_phase, in->value.residual); =} } - main reactor spatRecommender { - signal = new phaseChanger(); + signal = new phaseChanger() pub = new MQTTPublisher( - topic = "spat/rsu101", - //address = "tcp://host.docker.internal:1883", - address = "tcp://localhost:1883", - clientID = "Intersection Signal 101", - include_physical_timestamp = 0 - ); - - signal.out -> pub.in; - + topic="spat/rsu101", + address="tcp://localhost:1883", // address = "tcp://host.docker.internal:1883", + clientID = "Intersection Signal 101", + include_physical_timestamp=0) + + signal.out -> pub.in } 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 00000000..5c6bd98d Binary files /dev/null and b/experiments/C/src/TugOfWar/Interface/assets/lf.png differ 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..1d357f30 --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/requirements.txt @@ -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 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..c99eaae4 --- /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 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. + 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(); + } + } + =} +} diff --git a/experiments/C/src/context-manager/ContextManager.lf b/experiments/C/src/context-manager/ContextManager.lf index 1523d611..89f8dfe6 100644 --- a/experiments/C/src/context-manager/ContextManager.lf +++ b/experiments/C/src/context-manager/ContextManager.lf @@ -1,39 +1,36 @@ /** - * Request-response pattern where a client asks for the sum of two numbers - * (a counter value plus 42). A response is provided by an AddService some random - * amount of logical time later. The client is able to match the response with the - * request so that, even though the response includes only the sum, the client knows - * which number was added to 42. - * - * To accomplish this, the client provides to the wrapper Manager the two - * numbers to add plus a "context" that is the client's own information that it wants - * back with the response. In this case, the context is is just its local counter value - * when it makes the request. - * - * This implementation predates generics in the C target, so all types here are int. - * Replace these with generics when available, to become function pointers, structs, etc. - * - * Also, for simplicity, the Manager limits the number of pending requests to - * 10 and does a very brute-force approximation to a hashtable. - * This would be easy to replace with an unbounded hash table. - * - * If the generics mechanism is further extended that the Manager can have a - * reactor class as a parameter, then the same Manager could wrap any number of - * different services. + * Request-response pattern where a client asks for the sum of two numbers (a counter value plus + * 42). A response is provided by an AddService some random amount of logical time later. The client + * is able to match the response with the request so that, even though the response includes only + * the sum, the client knows which number was added to 42. + * + * To accomplish this, the client provides to the wrapper Manager the two numbers to add plus a + * "context" that is the client's own information that it wants back with the response. In this + * case, the context is is just its local counter value when it makes the request. + * + * This implementation predates generics in the C target, so all types here are int. Replace these + * with generics when available, to become function pointers, structs, etc. + * + * Also, for simplicity, the Manager limits the number of pending requests to 10 and does a very + * brute-force approximation to a hashtable. This would be easy to replace with an unbounded hash + * table. + * + * If the generics mechanism is further extended that the Manager can have a reactor class as a + * parameter, then the same Manager could wrap any number of different services. */ target C { - timeout: 1s + timeout: 1 s } -import Random from "../../lib/Random.lf" -preamble {= - #include // FIXME: should not be needed. Should get from Random.lf. - + +import Random from "../../../../examples/C/src/lib/Random.lf" + +preamble {= // Pair of numbers to be added. typedef struct pair_t { int first; int second; } pair_t; - + // Request structure, input data and the context. typedef struct request_t { pair_t data; @@ -45,7 +42,7 @@ preamble {= request_t request; tag_t tag; } tagged_request_t; - + // Response data type, the sum plus the context. typedef struct response_t { int data; @@ -61,18 +58,19 @@ preamble {= // Interface definition for the service. reactor ServiceInterface { - input in:tagged_request_t - output out:tagged_response_t + input in: tagged_request_t + output out: tagged_response_t } -reactor AddService extends ServiceInterface, Random { - logical action pending:tagged_request_t - +reactor AddService extends ServiceInterface, Random { + logical action pending: tagged_request_t + reaction(in) -> pending {= // Random delay between 100 and 500 msec. interval_t delay = MSEC(1) * (random() % 400 + 100); lf_schedule_copy(pending, delay, &in->value, 1); =} + reaction(pending) -> out {= tagged_response_t response; response.response.data = pending->value.request.data.first + pending->value.request.data.second; @@ -81,14 +79,15 @@ reactor AddService extends ServiceInterface, Random { lf_set(out, response); =} } + reactor Manager { - input request:request_t - output response:response_t + input request: request_t + output response: response_t + + state pending: tagged_request_t[10] = {= { 0 } =} - state pending:tagged_request_t[10] = {= { 0 } =} - service = new AddService() - + reaction(startup) {= // Initialize brute force hashtable. for(int i = 0; i < 10; i++) { @@ -109,7 +108,7 @@ reactor Manager { } lf_print_error("Maximum number of pending requests exceeded."); =} - + reaction(service.out) -> response {= // Find the pending requests. BRUTE FORCE. for(int i = 0; i < 10; i++) { @@ -123,30 +122,32 @@ reactor Manager { lf_print_error("Pending requests not found."); =} } + reactor Client { - timer t(0, 100ms) - state counter:int = 0 - m = new Manager(); - + timer t(0, 100 ms) + state counter: int = 0 + m = new Manager() + reaction(t) -> m.request {= lf_print("Client asks what %d + 42 is.", self->counter); - + pair_t to_sum = { - .first = self->counter, + .first = self->counter, .second = 42 }; request_t request = { - .data = to_sum, + .data = to_sum, .context = self->counter }; lf_set(m.request, request); self->counter++; =} - + reaction(m.response) {= lf_print("Result: %d + 42 = %d.", m.response->value.context, m.response->value.data); =} } + main reactor { - c = new Client(); + c = new Client() } diff --git a/experiments/C/src/context-manager/ContextManager2.lf b/experiments/C/src/context-manager/ContextManager2.lf index 5575a720..c829a5e1 100644 --- a/experiments/C/src/context-manager/ContextManager2.lf +++ b/experiments/C/src/context-manager/ContextManager2.lf @@ -1,20 +1,20 @@ /** - * Request-response pattern like ContextManager except that a single service - * is shared among multiple clients. + * Request-response pattern like ContextManager except that a single service is shared among + * multiple clients. */ target C { - timeout: 1s + timeout: 1 s } -import Random from "../../lib/Random.lf" -preamble {= - #include // FIXME: should not be needed. Should get from Random.lf. - + +import Random from "../../../../examples/C/src/lib/Random.lf" + +preamble {= // Pair of numbers to be added. typedef struct pair_t { int first; int second; } pair_t; - + // Request structure, input data and the context. typedef struct request_t { pair_t data; @@ -27,7 +27,7 @@ preamble {= tag_t tag; int channel; } tagged_request_t; - + // Response data type, the sum plus the context. typedef struct response_t { int data; @@ -44,27 +44,27 @@ preamble {= // Interface definition for client reactor ClientInterface { - output request:request_t - input response:response_t + output request: request_t + input response: response_t } // Mirror image of client interface for manager. reactor ManagerInterface { - input request:request_t - output response:response_t - output trequest:tagged_request_t - input tresponse:tagged_response_t + input request: request_t + output response: response_t + output trequest: tagged_request_t + input tresponse: tagged_response_t } // Interface definition for the service. -reactor ServiceInterface(width:int = 2) { - input[width] in:tagged_request_t - output[width] out:tagged_response_t +reactor ServiceInterface(width: int = 2) { + input[width] in: tagged_request_t + output[width] out: tagged_response_t } -reactor AddService extends ServiceInterface, Random { - logical action pending:tagged_request_t - +reactor AddService extends ServiceInterface, Random { + logical action pending: tagged_request_t + reaction(pending) -> out {= tagged_response_t response; response.response.data = pending->value.request.data.first + pending->value.request.data.second; @@ -72,6 +72,7 @@ reactor AddService extends ServiceInterface, Random { response.tag = pending->value.tag; lf_set(out[pending->value.channel], response); =} + reaction(in) -> pending {= for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { @@ -86,9 +87,8 @@ reactor AddService extends ServiceInterface, Random { } reactor Manager extends ManagerInterface { + state pending: tagged_request_t[10] = {= { 0 } =} - state pending:tagged_request_t[10] = {= { 0 } =} - reaction(startup) {= // Initialize brute force hashtable. for(int i = 0; i < 10; i++) { @@ -109,7 +109,7 @@ reactor Manager extends ManagerInterface { } lf_print_error("Maximum number of pending requests exceeded."); =} - + reaction(tresponse) -> response {= // Find the pending requests. BRUTE FORCE. for(int i = 0; i < 10; i++) { @@ -123,37 +123,38 @@ reactor Manager extends ManagerInterface { lf_print_error("Pending requests not found."); =} } -reactor Client(start:int = 0) extends ClientInterface { - - timer t(0, 100ms) - state counter:int = start - + +reactor Client(start: int = 0) extends ClientInterface { + timer t(0, 100 ms) + state counter: int = start + reaction(t) -> request {= lf_print("Client asks what %d + 42 is.", self->counter); - + pair_t to_sum = { - .first = self->counter, + .first = self->counter, .second = 42 }; request_t req = { - .data = to_sum, + .data = to_sum, .context = self->counter }; lf_set(request, req); self->counter++; =} - + reaction(response) {= lf_print("Result: %d + 42 = %d.", response->value.context, response->value.data); =} } + main reactor { - c1 = new Client(); - c2 = new Client(start = 10); - m1 = new Manager(); - m2 = new Manager(); + c1 = new Client() + c2 = new Client(start=10) + m1 = new Manager() + m2 = new Manager() service = new AddService() - + c1.request -> m1.request m1.response -> c1.response c2.request -> m2.request diff --git a/experiments/C/src/protection-relay/ProtectionRelay.lf b/experiments/C/src/protection-relay/ProtectionRelay.lf index 9566b998..68887f09 100644 --- a/experiments/C/src/protection-relay/ProtectionRelay.lf +++ b/experiments/C/src/protection-relay/ProtectionRelay.lf @@ -1,128 +1,135 @@ /** - * This models is a sketch (with no functionality currently) - * with the structure of a protection relay design from: - * - * > Mathieu Jan, Vincent David, Jimmy Lalande, Maurice Pitel, - * "Usage of the safety-oriented real-time OASIS approach to - * build deterministic protection relays," - * Symposium on Industrial Embedded System (SIES), - * July 7-9, 2010, Trento, Italy, DOI: 10.1109/SIES.2010.5551378 - * + * This models is a sketch (with no functionality currently) with the structure of a protection + * relay design from: + * + * > Mathieu Jan, Vincent David, Jimmy Lalande, Maurice Pitel, "Usage of the safety-oriented + * real-time OASIS approach to build deterministic protection relays," Symposium on Industrial + * Embedded System (SIES), July 7-9, 2010, Trento, Italy, DOI: 10.1109/SIES.2010.5551378 + * * The authors are from CEA-LIST and Schneider Electric. - * - * In section V.B, the paper explains that the AgTRS, Ag50, and Ag51 - * components have to be invoked at 6660us intervals (12x555) rather - * than 13320us (24x555), alternating each other's computation in order - * to meet the 26640us deadline requirement for detecting a fault. - * This is because, apparently, OASIS uses a LET model, where each - * component reads the inputs provided by its upstream component in - * the previous cycle. Hence, the cascade of two components triggered - * at 13320us would already introduce a delay of 13320us (the second - * component will automatically see data that is 13320us old). - * That second component is allowed an execution time of 13320us, - * which puts us right at the deadline boundary. The additional delay - * caused by the LET of AgMoy puts us over the deadline. - * - * This problem does not occur with LF because the components are - * triggered logically simultaneously but in sequence, so as long - * as the total computation time along the path from AgARGA to - * Ag50 and Ag51 is less than 26640us, the deadline will be met. - * This requirement is expressed as deadlines. - * - * The AdRMS component, which runs at a much slower rate (once per - * second), is apparently not used by the protection relay. - * Since it has no deadline, an EDF scheduler will give it lower - * priority. To avoid interfering with the protection relay, however, - * it will require a LET or scheduling enclave method to be able - * to run in parallel, allowing time to advance at the fine grain - * level without having to complete its computation. + * + * In section V.B, the paper explains that the AgTRS, Ag50, and Ag51 components have to be invoked + * at 6660us intervals (12x555) rather than 13320us (24x555), alternating each other's computation + * in order to meet the 26640us deadline requirement for detecting a fault. This is because, + * apparently, OASIS uses a LET model, where each component reads the inputs provided by its + * upstream component in the previous cycle. Hence, the cascade of two components triggered at + * 13320us would already introduce a delay of 13320us (the second component will automatically see + * data that is 13320us old). That second component is allowed an execution time of 13320us, which + * puts us right at the deadline boundary. The additional delay caused by the LET of AgMoy puts us + * over the deadline. + * + * This problem does not occur with LF because the components are triggered logically simultaneously + * but in sequence, so as long as the total computation time along the path from AgARGA to Ag50 and + * Ag51 is less than 26640us, the deadline will be met. This requirement is expressed as deadlines. + * + * The AdRMS component, which runs at a much slower rate (once per second), is apparently not used + * by the protection relay. Since it has no deadline, an EDF scheduler will give it lower priority. + * To avoid interfering with the protection relay, however, it will require a LET or scheduling + * enclave method to be able to run in parallel, allowing time to advance at the fine grain level + * without having to complete its computation. */ - target C + @label("Acquisition stage") reactor AgARGA { - output DataBufferI:double[4] + output DataBufferI: double[4] timer t(0, 555 us) + reaction(t) -> DataBufferI {= // Output something. =} } + @label("Accumulate") reactor AgCumulRMS { - input currents:double[4] - output VS_TRS_Cumu1:double[4] - output VS_TRS_Cumu2:double[4] + input currents: double[4] + output VS_TRS_Cumu1: double[4] + output VS_TRS_Cumu2: double[4] + reaction(currents) -> VS_TRS_Cumu1, VS_TRS_Cumu2 {= // Calculate sum and the square sum of num_items data items. // Outputs updated every time, but only used every 1800 times. =} } + // The AgRMS component does not seem to be used in protection. // Is this just for UI displays? @label("RMS") reactor AgRMS { - timer t(0, 999ms) // Activate periodically every 1800 samples (1800*555us) - input sum:double[4] - input squared_sum:double[4] - input VS_Mod2:double // FIXME: Is this an input or an output? + timer t(0, 999 ms) // Activate periodically every 1800 samples (1800*555us) + input sum: double[4] + input squared_sum: double[4] + input VS_Mod2: double // FIXME: Is this an input or an output? + reaction(t) sum, squared_sum, VS_Mod2 {= // Calculate root mean square of the four currents =} } + @label("Crest value") reactor AgCrete { - input currents:double[4] - output V_DETC:double[4] + input currents: double[4] + output V_DETC: double[4] + reaction(currents) -> V_DETC {= // Compute crest values. Update output on each input. =} } + @label("Average") -reactor AgMoy(num_values:int(3)) { - input currents:double[4] - output V_TRS_Cumu1Filtree:double[4] +reactor AgMoy(num_values: int = 3) { + input currents: double[4] + output V_TRS_Cumu1Filtree: double[4] + reaction(currents) -> V_TRS_Cumu1Filtree {= // Average num_values =} } + @label("Magnitude of fundamental and harmonics") -reactor AgTRS(num_values:int(12)) { - timer t(0, 13320us) // Computes an output every 24 periods of 555us. - input average:double[4] - input crest:double[4] - state avghistory:double[12]({= [0] =}) - output V_mod2Imax:double[4] - output VS_Mod2:double[4] + +reactor AgTRS(num_values: int = 12) { + timer t(0, 13320 us) // Computes an output every 24 periods of 555us. + input average: double[4] + input crest: double[4] + state avghistory: double[12] = {= [0] =} + output V_mod2Imax: double[4] + output VS_Mod2: double[4] + reaction(average) {= // Record the input, update avghistory =} - reaction(t) crest -> V_mod2Imax {= + + reaction(t) crest -> V_mod2Imax {= // Use last avghistory inputs and the most - // recent value of crest to calculate, - // but activate every 2*num_values inputs. - // ??? "Nevertheless, its clock ticks every num_values inputs. + // recent value of crest to calculate, + // but activate every 2*num_values inputs. + // ??? "Nevertheless, its clock ticks every num_values inputs. =} } + reactor Ag50 { - timer t(0, 13320us) // Computes an output every 24 periods of 555us. - // Or should this be 666us (12 times 555)? - input in:double[4] + timer t(0, 13320 us) // Computes an output every 24 periods of 555us. + input in: double[4] // Or should this be 666us (12 times 555)? + reaction(t) in {= // Calculate whether to open the relay. - =} deadline(26640us) {= + =} deadline(26640 us) {= // Deadline missed! =} } + reactor Ag51 { - timer t(0, 13320us) // Computes an output every 24 periods of 555us. - // Or should this be 666us (12 times 555)? - input in:double[4] + timer t(0, 13320 us) // Computes an output every 24 periods of 555us. + input in: double[4] // Or should this be 666us (12 times 555)? + reaction(t) in {= // Calculate whether to open the relay. - =} deadline(26640us) {= + =} deadline(26640 us) {= // Deadline missed! =} } + main reactor { acquisition = new AgARGA() accumulate = new AgCumulRMS() @@ -132,7 +139,7 @@ main reactor { magnitude = new AgTRS() protection1 = new Ag50() protection2 = new Ag51() - + acquisition.DataBufferI -> accumulate.currents accumulate.VS_TRS_Cumu1 -> rms.sum accumulate.VS_TRS_Cumu2 -> rms.squared_sum diff --git a/experiments/C/src/soafee/soafee.lf b/experiments/C/src/soafee/soafee.lf index 5bea9054..1b2b6da5 100644 --- a/experiments/C/src/soafee/soafee.lf +++ b/experiments/C/src/soafee/soafee.lf @@ -1,12 +1,11 @@ -target C{ +target C { keepalive: true -}; +} + +reactor Display(width: int = 4) { + input[width] obstacleList: int // Center/HUD/Cluster indicating direction of obstacle + output angleOfObstacle: int[] -reactor Display(width: int(4)){ - //Center/HUD/Cluster indicating direction of obstacle - input[width] obstacleList:int - output angleOfObstacle:int[] - reaction(startup) -> angleOfObstacle {= for (int i = 0; i < 4; i++) { angleOfObstacle->value[i] = 0; @@ -14,7 +13,7 @@ reactor Display(width: int(4)){ SET_PRESENT(angleOfObstacle); =} - reaction(obstacleList)->angleOfObstacle{= + reaction(obstacleList) -> angleOfObstacle {= //TODO: Handling of graphics //0 - left front (135) //1 - right front (45) @@ -28,18 +27,18 @@ reactor Display(width: int(4)){ if (obstacleList[i]->is_present && obstacleList[i]->value) { //lf_set(angleOfObstacle[i], 1); - + lf_sleep(MSEC(100)); } } =} } -reactor Speaker(width: int(4)){ - //3D sound generation - input[width] obstacleList:int - output alert:bool - reaction(obstacleList)->alert{= +reactor Speaker(width: int = 4) { + input[width] obstacleList: int // 3D sound generation + output alert: bool + + reaction(obstacleList) -> alert {= //Handling of device audio instant_t processing_delay = MSEC(30); lf_sleep(processing_delay); @@ -48,49 +47,49 @@ reactor Speaker(width: int(4)){ if (obstacleList[i]->is_present && obstacleList[i]->value){ lf_set(alert, true); } - else{ + else{ lf_set(alert, false); } } =} } -reactor DistanceSensor(bank_index: int(0)){ - timer t(0, 10 ms); - physical action pa; - output range:int; //in meters +reactor DistanceSensor(bank_index: int = 0) { + timer t(0, 10 ms) + physical action pa + output range: int // in meters - reaction(t)->pa{= + reaction(t) -> pa {= //Schedule scan lf_schedule(pa, MSEC(5000)); =} - reaction(pa)->range{= + reaction(pa) -> range {= int distance = rand() % 25; lf_set(range, distance); =} } -reactor Monitor{ - mutable input display_event:int[]; - input speaker_event:bool; +reactor Monitor { + mutable input display_event: int[] + input speaker_event: bool - reaction(display_event){= + reaction(display_event) {= for (int i = 0; i < display_event->length; i++) { lf_print("Display: %d", display_event->value[i]); } =} - reaction(speaker_event){= + reaction(speaker_event) {= lf_print("Speaker: %d", speaker_event->value); =} } -reactor ProcessSignals(width: int(4)){ - input[width] rangeList:int - output[width] obstacles:int - - reaction(rangeList)->obstacles{= +reactor ProcessSignals(width: int = 4) { + input[width] rangeList: int + output[width] obstacles: int + + reaction(rangeList) -> obstacles {= for (int i = 0; i < rangeList_width; i++) { //0 - left front //1 - right front @@ -109,16 +108,13 @@ reactor ProcessSignals(width: int(4)){ } main reactor { - #speaker = new Speaker() - display = new Display() + display = new Display() // speaker = new Speaker() ds = new[4] DistanceSensor() ps = new ProcessSignals(width=4) + // ps.obstacles -> display.obstacleList + // ps.obstacles -> speaker.obstacleList + // monitor = new Monitor() + // display.angleOfObstacle -> monitor.display_event + // speaker.alert -> monitor.speaker_event ds.range -> ps.rangeList - - #ps.obstacles -> display.obstacleList - #ps.obstacles -> speaker.obstacleList - - #monitor = new Monitor() - #display.angleOfObstacle -> monitor.display_event - #speaker.alert -> monitor.speaker_event } diff --git a/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf b/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf index e21400b8..775c19ee 100644 --- a/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf +++ b/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Receiver.lf @@ -24,5 +24,7 @@ reactor Receiver { self->subscriber_node->topic_callback(in->value); =} - reaction(shutdown) {= rclcpp::shutdown(); =} + reaction(shutdown) {= + rclcpp::shutdown(); + =} } diff --git a/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf b/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf index 88f92246..ea049912 100644 --- a/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf +++ b/experiments/CCpp/src/ROS/MigrationGuide/lf-project/src/Sender.lf @@ -28,5 +28,7 @@ reactor Sender { lf_set(out, message); =} - reaction(shutdown) {= rclcpp::shutdown(); =} + reaction(shutdown) {= + rclcpp::shutdown(); + =} } diff --git a/experiments/Python/src/FurutaPendulum/FurutaPendulumStabilize.lf b/experiments/Python/src/FurutaPendulum/FurutaPendulumStabilize.lf index c41eb033..5c19e19a 100644 --- a/experiments/Python/src/FurutaPendulum/FurutaPendulumStabilize.lf +++ b/experiments/Python/src/FurutaPendulum/FurutaPendulumStabilize.lf @@ -3,29 +3,32 @@ # the stabilize-mode control law. # # Need to install matplotlib library: -# pip install matplotlib -# +# `pip install matplotlib` +# # @author Edward A. Lee target Python { timeout: 8 secs, fast: true } + import PendulumSimulation from "PendulumSimulationRK4.lf" # Alternatively, change the ODE solver to forward-Euler by calling: # import PendulumSimulation from "PendulumSimulationEuler.lf" - preamble {= import matplotlib.pyplot as plt =} + reactor Plotter { input data - state times({=[]=}) - state values({=[]=}) + state times = {= [] =} + state values = {= [] =} + reaction(data) {= self.times.append(lf.time.logical_elapsed()/1e9) self.values.append(data.value) =} + reaction(shutdown) {= fig, axs = plt.subplots() axs.set_title("Pendulum Angle") @@ -35,12 +38,8 @@ reactor Plotter { plt.show() =} } -reactor PendulumController( - g1(1.7), - g2(0.3), - g3(0.03), - g4(0.06) -) { + +reactor PendulumController(g1=1.7, g2=0.3, g3=0.03, g4=0.06) { input theta input d_theta input phi @@ -59,13 +58,12 @@ reactor PendulumController( } main reactor { - timer d(3 sec); // One-shot disturbance at 3 seconds. + timer d(3 sec) # One-shot disturbance at 3 seconds. - p = new PendulumSimulation(initial_theta = 0.1) + p = new PendulumSimulation(initial_theta=0.1) c = new PendulumController() plt = new Plotter() - p.theta, p.d_theta, p.phi, p.d_phi - -> c.theta, c.d_theta, c.phi, c.d_phi + p.theta, p.d_theta, p.phi, p.d_phi -> c.theta, c.d_theta, c.phi, c.d_phi c.control -> p.u p.theta -> plt.data diff --git a/experiments/Python/src/FurutaPendulum/PendulumSimulationEuler.lf b/experiments/Python/src/FurutaPendulum/PendulumSimulationEuler.lf index fa596b7b..8cddb8e8 100644 --- a/experiments/Python/src/FurutaPendulum/PendulumSimulationEuler.lf +++ b/experiments/Python/src/FurutaPendulum/PendulumSimulationEuler.lf @@ -1,66 +1,57 @@ -target Python; +target Python + /** - * A simple forward-Euler simulation of a Furuta pendulum, - * based on the Ptolemy II model constructed by Johan Eker - * and described in this paper: - * - * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, - * “Realistic simulations of embedded control systems,” - * IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. - * - * The Ptolemy II model is more accurate because it uses an - * RK-45 solver, but this is adequate for many purposes. - * - * This outputs its state every `sample_period`. - * It updates the state before outputting it - * using the most recently received control input. - * - * The `theta` output is the angle of the pendulum, - * which is 0 when the pendulum is pointing straight up, - * and `d_theta` is its initial angular velocity. - * The `phi` output is the angle of the horizontal - * arm and `d_phi` is its angular velocity. + * A simple forward-Euler simulation of a Furuta pendulum, based on the Ptolemy II model constructed + * by Johan Eker and described in this paper: + * + * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, “Realistic simulations of embedded control + * systems,” IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. + * + * The Ptolemy II model is more accurate because it uses an RK-45 solver, but this is adequate for + * many purposes. + * + * This outputs its state every `sample_period`. It updates the state before outputting it using the + * most recently received control input. * - * The `u` input is the control input, which applies - * torque to the arm. The `d` input is an impulsive - * disturbance applied to the pendulum, as if you were - * to tap it with a hard object. When an input is - * received on `d`, its value provides an instantaneous - * increment or decrement to `d_theta`. - * Notice that no output is produced when an input - * is received on `d` unless that input is simultaneous - * with the sampling period. The effect of the disturbance - * will not be seen on the outputs until the next sample, - * as would usually be the case for digital controller. + * The `theta` output is the angle of the pendulum, which is 0 when the pendulum is pointing + * straight up, and `d_theta` is its initial angular velocity. The `phi` output is the angle of the + * horizontal arm and `d_phi` is its angular velocity. + * + * The `u` input is the control input, which applies torque to the arm. The `d` input is an + * impulsive disturbance applied to the pendulum, as if you were to tap it with a hard object. When + * an input is received on `d`, its value provides an instantaneous increment or decrement to + * `d_theta`. Notice that no output is produced when an input is received on `d` unless that input + * is simultaneous with the sampling period. The effect of the disturbance will not be seen on the + * outputs until the next sample, as would usually be the case for digital controller. * * @author Edward A. Lee */ preamble {= import math =} + reactor PendulumSimulation( - initial_theta(-3.14159), # Initial pendulum angle. - sample_period(5 msec), # Sample period. - g(9.81), # Acceleration of gravity. - alpha(0.00260569), - beta(0.05165675), - gamma(9.7055e-4), - epsilon(0.08103060) -){ - input u # Control input. - input d # Impulsive disturbance - - output theta # Pendulum angle. - output d_theta # Pendulum angular velocity. - output phi # Arm angle. - output d_phi # Arm angular velocity. - + initial_theta=-3.14159, # Initial pendulum angle. + sample_period = 5 msec, # Sample period. + g=9.81, # Acceleration of gravity. + alpha=0.00260569, + beta=0.05165675, + gamma=9.7055e-4, + epsilon=0.08103060) { + input u # Control input. + input d # Impulsive disturbance + + output theta # Pendulum angle. + output d_theta # Pendulum angular velocity. + output phi # Arm angle. + output d_phi # Arm angular velocity. + state x(0.0, 0.0, 0.0, 0.0) - state first(True) - state latest_u(0.0) - + state first = True + state latest_u = 0.0 + timer t(0, sample_period) - + reaction(d) {= # NOTE: Here, we effectively shift the disturbance to be # simultaneous with the next sample. A more accurate model @@ -101,14 +92,14 @@ reactor PendulumSimulation( * math.cos(self.x[0]) * self.g * self.latest_u - + + + (self.alpha * self.beta + math.pow(self.alpha * math.sin(self.x[0]), 2.0)) * self.epsilon / self.alpha * math.sin(self.x[0]) ) ) x2_dot = self.x[3] x3_dot = ((1.0 / ( - self.alpha * self.beta + self.alpha * self.beta + math.pow(self.alpha * math.sin(self.x[0]), 2.0) - math.pow(self.gamma * math.cos(self.x[0]), 2.0) )) * ( @@ -148,13 +139,14 @@ reactor PendulumSimulation( else: self.x[0] = self.initial_theta self.first = False - + # Output the state. theta.set(self.x[0]) d_theta.set(self.x[1]) phi.set(self.x[2]) d_phi.set(self.x[3]) =} + reaction(u) {= self.latest_u = u.value =} diff --git a/experiments/Python/src/FurutaPendulum/PendulumSimulationRK4.lf b/experiments/Python/src/FurutaPendulum/PendulumSimulationRK4.lf index 890031af..51d95545 100644 --- a/experiments/Python/src/FurutaPendulum/PendulumSimulationRK4.lf +++ b/experiments/Python/src/FurutaPendulum/PendulumSimulationRK4.lf @@ -1,70 +1,61 @@ -target Python; +target Python + /** - * A simple forward-Euler simulation of a Furuta pendulum, - * based on the Ptolemy II model constructed by Johan Eker - * and described in this paper: - * - * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, - * “Realistic simulations of embedded control systems,” - * IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. - * - * The Ptolemy II model is more accurate because it uses an - * RK-45 solver, but this is adequate for many purposes. - * - * This outputs its state every `sample_period`. - * It updates the state before outputting it - * using the most recently received control input. - * - * The `theta` output is the angle of the pendulum, - * which is 0 when the pendulum is pointing straight up, - * and `d_theta` is its initial angular velocity. - * The `phi` output is the angle of the horizontal - * arm and `d_phi` is its angular velocity. + * A simple forward-Euler simulation of a Furuta pendulum, based on the Ptolemy II model constructed + * by Johan Eker and described in this paper: + * + * J. Liu, J. Eker, J. W. Janneck, and E. A. Lee, “Realistic simulations of embedded control + * systems,” IFAC Proceedings Volumes, vol. 35, no. 1, pp. 391–396, 2002. + * + * The Ptolemy II model is more accurate because it uses an RK-45 solver, but this is adequate for + * many purposes. * - * The `u` input is the control input, which applies - * torque to the arm. The `d` input is an impulsive - * disturbance applied to the pendulum, as if you were - * to tap it with a hard object. When an input is - * received on `d`, its value provides an instantaneous - * increment or decrement to `d_theta`. - * Notice that no output is produced when an input - * is received on `d` unless that input is simultaneous - * with the sampling period. The effect of the disturbance - * will not be seen on the outputs until the next sample, - * as would usually be the case for digital controller. + * This outputs its state every `sample_period`. It updates the state before outputting it using the + * most recently received control input. + * + * The `theta` output is the angle of the pendulum, which is 0 when the pendulum is pointing + * straight up, and `d_theta` is its initial angular velocity. The `phi` output is the angle of the + * horizontal arm and `d_phi` is its angular velocity. + * + * The `u` input is the control input, which applies torque to the arm. The `d` input is an + * impulsive disturbance applied to the pendulum, as if you were to tap it with a hard object. When + * an input is received on `d`, its value provides an instantaneous increment or decrement to + * `d_theta`. Notice that no output is produced when an input is received on `d` unless that input + * is simultaneous with the sampling period. The effect of the disturbance will not be seen on the + * outputs until the next sample, as would usually be the case for digital controller. * * @author Edward A. Lee, Yiwei Yu */ preamble {= import math =} + reactor PendulumSimulation( - initial_theta(-3.14159), # Initial pendulum angle. - sample_period(5 msec), # Sample period. - g(9.81), # Acceleration of gravity. - alpha(0.00260569), - beta(0.05165675), - gamma(9.7055e-4), - epsilon(0.08103060) -){ - input u # Control input. - input d # Impulsive disturbance - - output theta # Pendulum angle. - output d_theta # Pendulum angular velocity. - output phi # Arm angle. - output d_phi # Arm angular velocity. - + initial_theta=-3.14159, # Initial pendulum angle. + sample_period = 5 msec, # Sample period. + g=9.81, # Acceleration of gravity. + alpha=0.00260569, + beta=0.05165675, + gamma=9.7055e-4, + epsilon=0.08103060) { + input u # Control input. + input d # Impulsive disturbance + + output theta # Pendulum angle. + output d_theta # Pendulum angular velocity. + output phi # Arm angle. + output d_phi # Arm angular velocity. + state x(0.0, 0.0, 0.0, 0.0) - state first(True) - state latest_u(0.0) - state elapsed_time(0) + state first = True + state latest_u = 0.0 + state elapsed_time = 0 timer t(0, sample_period) - # Based on the dynamic model of furuta pendulum, + # Based on the dynamic model of furuta pendulum, # define the differential equations - method calculate_derivatives(x){= + method calculate_derivatives(x) {= x0_dot = self.x[1] x1_dot = (1.0/( self.alpha * self.beta @@ -92,14 +83,14 @@ reactor PendulumSimulation( * math.cos(self.x[0]) * self.g * self.latest_u - + + + (self.alpha * self.beta + math.pow(self.alpha * math.sin(self.x[0]), 2.0)) * self.epsilon / self.alpha * math.sin(self.x[0]) ) ) x2_dot = self.x[3] x3_dot = ((1.0 / ( - self.alpha * self.beta + self.alpha * self.beta + math.pow(self.alpha * math.sin(self.x[0]), 2.0) - math.pow(self.gamma * math.cos(self.x[0]), 2.0) )) * ( @@ -136,7 +127,7 @@ reactor PendulumSimulation( # Method used to update the state method update_state() {= - # The time interval is defined as the time elapsed + # The time interval is defined as the time elapsed # since last time we output the state time_interval = (lf.time.logical_elapsed() - self.elapsed_time) * 1e-9 self.elapsed_time = lf.time.logical_elapsed() @@ -158,7 +149,7 @@ reactor PendulumSimulation( x[i] += (h/6.)*(k1[i] + 2*k2[i] + 2*k3[i] + k4[i]) return x - # Update x using RK4 + # Update x using RK4 self.x = RK4_ODE(self.x) =} @@ -169,10 +160,10 @@ reactor PendulumSimulation( phi.set(self.x[2]) d_phi.set(self.x[3]) =} - + # The update will be triggered when either a disturbance is introduced or a sample - # period of time has elapsed. - # See SmallTest.lf for how disturbance updates works in a simpler forward euler + # period of time has elapsed. + # See SmallTest.lf for how disturbance updates works in a simpler forward euler # example reaction(d) {= self.update_state() @@ -187,10 +178,9 @@ reactor PendulumSimulation( self.first = False # Output the state. self.output_state(theta, d_theta, phi, d_phi) - =} + =} reaction(u) {= self.latest_u = u.value =} - } diff --git a/experiments/Python/src/FurutaPendulum/SmallTest.lf b/experiments/Python/src/FurutaPendulum/SmallTest.lf index 5c5a8bd6..75d40138 100644 --- a/experiments/Python/src/FurutaPendulum/SmallTest.lf +++ b/experiments/Python/src/FurutaPendulum/SmallTest.lf @@ -1,16 +1,14 @@ /** - * This Lingua Franca program models a simple simulation and visualization of a - * physical system subjected to an impulsive disturbance. The Simulation reactor - * generates the state of the system at regular intervals specified by the sample - * period, which it updates based on the time elapsed since the last state update. - * It also incorporates any disturbances, which are modeled as instantaneous - * changes in the rate of change of the system state. These states are then sent to - * the Plotter reactor, which collects these data points over time. Upon the - * termination of the simulation, the Plotter reactor generates a plot displaying - * the state of the system over time, marking each data point with its respective - * time and value, and indicating the disturbances with stem lines. The main - * reactor triggers a one-shot disturbance three and a half seconds into the - * simulation. + * This Lingua Franca program models a simple simulation and visualization of a physical system + * subjected to an impulsive disturbance. The Simulation reactor generates the state of the system + * at regular intervals specified by the sample period, which it updates based on the time elapsed + * since the last state update. It also incorporates any disturbances, which are modeled as + * instantaneous changes in the rate of change of the system state. These states are then sent to + * the Plotter reactor, which collects these data points over time. Upon the termination of the + * simulation, the Plotter reactor generates a plot displaying the state of the system over time, + * marking each data point with its respective time and value, and indicating the disturbances with + * stem lines. The main reactor triggers a one-shot disturbance three and a half seconds into the + * simulation. */ target Python { timeout: 8 secs, @@ -23,24 +21,24 @@ preamble {= =} reactor Simulation( - sample_period(1 secs), # Sample period. - g(9.81) # Acceleration of gravity. -){ - input u # Control input. - input d # Impulsive disturbance - output result # Output value for y axis - output d_result # Rate of change of y - + sample_period = 1 secs, # Sample period. + # Acceleration of gravity. + g=9.81) { + input u # Control input. + input d # Impulsive disturbance + output result # Output value for y axis + output d_result # Rate of change of y + state x(0.0, 1.0) - state first(False) - state latest_u(0.0) - state elapsed_time(0) + state first = False + state latest_u = 0.0 + state elapsed_time = 0 timer t(0, sample_period) # Method used to update the state method update_state() {= - # The time interval is defined as the time elapsed + # The time interval is defined as the time elapsed # since last time we output the state time_interval = (lf.time.logical_elapsed() - self.elapsed_time)/1e9 self.elapsed_time = lf.time.logical_elapsed() @@ -48,10 +46,10 @@ reactor Simulation( # Perform Simple Euler, return the updated x def Simple_Euler(x): h = time_interval - x[0] = x[0] + h*x[1] + x[0] = x[0] + h*x[1] return x - # Update x using RK4 + # Update x using RK4 self.x = Simple_Euler(self.x) =} @@ -60,9 +58,9 @@ reactor Simulation( result.set(self.x[0]) d_result.set(self.x[1]) =} - + # The update will be triggered when either a disturbance is introduced or a sample - # period of time has elapsed. + # period of time has elapsed. reaction(d) {= self.update_state() self.x[1] += d.value @@ -72,18 +70,19 @@ reactor Simulation( self.update_state() # Output the state. self.output_state(result, d_result) - =} - + =} } reactor Plotter { input data - state times({=[]=}) - state values({=[]=}) + state times = {= [] =} + state values = {= [] =} + reaction(data) {= self.times.append(lf.time.logical_elapsed()/1e9) self.values.append(data.value) =} + reaction(shutdown) {= fig, axs = plt.subplots() axs.set_title("Small Test Graph") @@ -105,7 +104,7 @@ reactor Plotter { } main reactor { - timer d(3500 msec); // One-shot disturbance at 3 seconds. + timer d(3500 msec) # One-shot disturbance at 3 seconds. p = new Simulation() plt = new Plotter() diff --git a/experiments/Python/src/GeneralModels/bankedsimius.lf b/experiments/Python/src/GeneralModels/bankedsimius.lf index 48d6bf6e..82832576 100644 --- a/experiments/Python/src/GeneralModels/bankedsimius.lf +++ b/experiments/Python/src/GeneralModels/bankedsimius.lf @@ -1,60 +1,50 @@ -/*Banked version of a human distributed system */ - -target Python {}; +/** Banked version of a human distributed system */ +target Python preamble {= - specs = [ { - + }, { - + }, { - + } ] =} ### Sensory reactor Sensory { - state _reading - + output out0 - - timer t(0, 100msec) - - reaction(startup) {= - - =} - + + timer t(0, 100 msec) + + reaction(startup) {= =} + reaction(t) -> out0 {= - out0.set() =} } ### Brain reactor Brain { - state _active - + input in0 input in1 input in2 - + output out0 output out1 output out2 - - reaction(startup) {= - - =} - + + reaction(startup) {= =} + reaction(in0, in1, in2) -> out0, out1, out2 {= - out0.set() out1.set() out2.set() @@ -63,26 +53,20 @@ reactor Brain { ### Actuators reactor Movers { - state _position - + input in0 - - reaction(startup) {= - - =} - - reaction(in0) {= - - =} + + reaction(startup) {= =} + + reaction(in0) {= =} } main reactor { - nerves = new[3] Sensory() brain = new Brain() movers = new[3] Movers() - + nerves.out0 -> brain.in0, brain.in1, brain.in2 brain.out0, brain.out1, brain.out2 -> movers.in0 } diff --git a/experiments/Python/src/GeneralModels/headedswarm.lf b/experiments/Python/src/GeneralModels/headedswarm.lf index 0a5e054d..f9e92938 100644 --- a/experiments/Python/src/GeneralModels/headedswarm.lf +++ b/experiments/Python/src/GeneralModels/headedswarm.lf @@ -1,104 +1,84 @@ -/* Swarm with head observer */ +/** Swarm with head observer */ +/** Swarm go crazy */ +target Python -/* Swarm go crazy */ - -target Python{}; - -preamble {= - -=} +preamble {= =} reactor Observer { - timer tick(0, 100msec) - + timer tick(0, 100 msec) + input in0 input in1 input in2 input in3 input in4 - - + state _node_zero state _node_one state _node_two state _node_three state _node_four - + output nodes - - reaction(tick) -> nodes {= - - =} - - reaction(in0, in1, in2, in3, in4) {= - - =} + + reaction(tick) -> nodes {= =} + + reaction(in0, in1, in2, in3, in4) {= =} } ### Unit -reactor Node(speed(100msec), index(0)) { - +reactor Node(speed = 100 msec, index=0) { state _node_index state _node_zero state _node_one state _node_two state _node_three - + input in0 input in1 input in2 input in3 input in4 - + output out0 output out1 output out2 output out3 output head - -// initial mode One { -// reaction(in4) {= -// Two.set() -// =} -// } mode Two { + + # initial mode One { + # reaction(in4) {= + # Two.set() + # =} + # } mode Two { timer tick(0, speed) - - reaction(tick) -> out0, out1, out2, out3 {= - - =} - - reaction(in0, in1, in2, in3){= - - =} -// } + + reaction(tick) -> out0, out1, out2, out3 {= =} + + reaction(in0, in1, in2, in3) {= =} # } } main reactor { node0 = new Node() - node1 = new Node(index = 1) - node2 = new Node(index = 2) - node3 = new Node(index = 3) - node4 = new Node(index = 4) - + node1 = new Node(index=1) + node2 = new Node(index=2) + node3 = new Node(index=3) + node4 = new Node(index=4) + head = new Observer() - + (head.nodes)+ -> node0.in4, node1.in4, node2.in4, node3.in4, node4.in4 - - node0.head, node1.head, node2.head, node3.head, node4.head -> - head.in0, head.in1, head.in2, head.in3, head.in4 - - node0.out0, node0.out1, node0.out2, node0.out3 -> - node1.in0, node2.in0, node3.in0, node4.in0 - - node1.out0, node1.out1, node1.out2, node1.out3 -> - node0.in0, node2.in1, node3.in1, node4.in1 - - node2.out0, node2.out1, node2.out2, node2.out3 -> - node0.in1, node1.in1, node3.in2, node4.in2 - - node3.out0, node3.out1, node3.out2, node3.out3 -> - node0.in2, node1.in2, node2.in2, node4.in3 - - node4.out0, node4.out1, node4.out2, node4.out3 -> - node0.in3, node1.in3, node2.in3, node3.in3 + + node0.head, node1.head, node2.head, node3.head, node4.head + -> head.in0, head.in1, head.in2, head.in3, head.in4 + + node0.out0, node0.out1, node0.out2, node0.out3 -> node1.in0, node2.in0, node3.in0, node4.in0 + + node1.out0, node1.out1, node1.out2, node1.out3 -> node0.in0, node2.in1, node3.in1, node4.in1 + + node2.out0, node2.out1, node2.out2, node2.out3 -> node0.in1, node1.in1, node3.in2, node4.in2 + + node3.out0, node3.out1, node3.out2, node3.out3 -> node0.in2, node1.in2, node2.in2, node4.in3 + + node4.out0, node4.out1, node4.out2, node4.out3 -> node0.in3, node1.in3, node2.in3, node3.in3 } diff --git a/experiments/Python/src/GeneralModels/queenant.lf b/experiments/Python/src/GeneralModels/queenant.lf index d8bdac69..557b3eab 100644 --- a/experiments/Python/src/GeneralModels/queenant.lf +++ b/experiments/Python/src/GeneralModels/queenant.lf @@ -1,50 +1,48 @@ -/* Model of reactors with one lead */ +/** Model of reactors with one lead */ +target Python -target Python; - -preamble {==} +preamble {= =} ### Ant reactor Ant { - timer t(0, 100msec) - state _standing(0) - + timer t(0, 100 msec) + state _standing = 0 + input in0 - + output out0 - + reaction(startup) -> out0 {= if self._standing == 0: out0.set(self._standing) self._standing += 1 =} - + reaction(t) -> out0 {= out0.set("Replace") =} - + reaction(in0) {= if in0.is_present: print("Input 0 is present.") =} } -reactor QueenAnt { - +reactor Queen { state _holding - + input in0 input in1 input in2 input in3 input in4 - + output out0 output out1 output out2 output out3 output out4 - + reaction(in0, in1, in2, in3, in4) -> out0, out1, out2, out3, out4 {= if in0.is_present: print("Input 0 is present") @@ -65,16 +63,10 @@ reactor QueenAnt { } main reactor { - ants = new[5] Ant() - queen = new QueenAnt() - - queen.out0, queen.out1, queen.out2, queen.out3, queen.out4 -> - ants.in0 - - ants.out0 -> queen.in0, queen.in1, queen.in2, queen.in3, queen.in4 -} - - + queen = new Queen() + queen.out0, queen.out1, queen.out2, queen.out3, queen.out4 -> ants.in0 + ants.out0 -> queen.in0, queen.in1, queen.in2, queen.in3, queen.in4 +} diff --git a/experiments/Python/src/GeneralModels/simius.lf b/experiments/Python/src/GeneralModels/simius.lf index 3df899bc..5b5c7678 100644 --- a/experiments/Python/src/GeneralModels/simius.lf +++ b/experiments/Python/src/GeneralModels/simius.lf @@ -1,30 +1,22 @@ -/* General Model of Reactors - * simulating interactions between human systems - */ - - target Python { - -}; - -preamble {= - -=} +/** General Model of Reactors simulating interactions between human systems */ +target Python + +preamble {= =} ### Sensory reactor Sense { - state _touch state _vision state _hearing state _position - + output collision output view output sound output placement - - timer t(0, 100msec) - + + timer t(0, 100 msec) + reaction(t) -> collision, view, sound, placement {= collision.set(self._touch) view.set(self._vision) @@ -35,16 +27,15 @@ reactor Sense { ### Decision Maker reactor Brain { - input collision input view input sound input placement - + output legs output torso output arms - + reaction(collision, view, sound, placement) -> legs, torso, arms {= if collision.is_present: print("collision") @@ -54,7 +45,7 @@ reactor Brain { print("sound") if placement.is_present: print("placement") - + var leg_change var torso_change var arms_change @@ -66,43 +57,28 @@ reactor Brain { ### Body reactor Body { - state _energy - + input legs input torso input arms - - reaction(startup) {= - - =} - - reaction(legs) {= - - =} - - reaction(torso) {= - - =} - - reaction(arms) {= - - =} - + + reaction(startup) {= =} + + reaction(legs) {= =} + + reaction(torso) {= =} + + reaction(arms) {= =} } main reactor { - sensory = new Sense() brainy = new Brain() corpo = new Body() - - sensory.collision, sensory.view, - sensory.sound, sensory.placement -> - brainy.collision, brainy.view, - brainy.sound, brainy.placement - - brainy.legs, brainy.torso, brainy.arms -> - corpo.legs, corpo.torso, corpo.arms -} + sensory.collision, sensory.view, sensory.sound, sensory.placement + -> brainy.collision, brainy.view, brainy.sound, brainy.placement + + brainy.legs, brainy.torso, brainy.arms -> corpo.legs, corpo.torso, corpo.arms +} diff --git a/experiments/Python/src/GeneralModels/swarm.lf b/experiments/Python/src/GeneralModels/swarm.lf index 5090b6ec..795c0337 100644 --- a/experiments/Python/src/GeneralModels/swarm.lf +++ b/experiments/Python/src/GeneralModels/swarm.lf @@ -1,64 +1,46 @@ -/* Swarm go crazy */ +/** Swarm go crazy */ +target Python -target Python{}; - -preamble {= - -=} +preamble {= =} ### Unit -reactor Node(speed(100msec), index(0)) { - - state _head_index(0) +reactor Node(speed = 100 msec, index=0) { + state _head_index = 0 state _node_index - + input in0 input in1 input in2 input in3 input in4 - + output out0 output out1 output out2 output out3 - -// initial mode One { -// reaction(in4) {= -// Two.set() -// =} -// } mode Two { + + # initial mode One { + # reaction(in4) {= + # Two.set() + # =} + # } mode Two { timer tick(0, speed) - - reaction(tick) -> out0, out1, out2, out3 {= - - =} - - reaction(in0, in1, in2, in3){= - - =} -// } + + reaction(tick) -> out0, out1, out2, out3 {= =} + + reaction(in0, in1, in2, in3) {= =} # } } main reactor { node0 = new Node() - node1 = new Node(index = 1) - node2 = new Node(index = 2) - node3 = new Node(index = 3) - node4 = new Node(index = 4) + node1 = new Node(index=1) + node2 = new Node(index=2) + node3 = new Node(index=3) + node4 = new Node(index=4) - node0.out0, node0.out1, node0.out2, node0.out3 -> - node1.in0, node2.in0, node3.in0, node4.in0 - - node1.out0, node1.out1, node1.out2, node1.out3 -> - node0.in0, node2.in1, node3.in1, node4.in1 - - node2.out0, node2.out1, node2.out2, node2.out3 -> - node0.in1, node1.in1, node3.in2, node4.in2 - - node3.out0, node3.out1, node3.out2, node3.out3 -> - node0.in2, node1.in2, node2.in2, node4.in3 - - node4.out0, node4.out1, node4.out2, node4.out3 -> - node0.in3, node1.in3, node2.in3, node3.in3 + node0.out0, node0.out1, node0.out2, node0.out3 -> node1.in0, node2.in0, node3.in0, node4.in0 + node1.out0, node1.out1, node1.out2, node1.out3 -> node0.in0, node2.in1, node3.in1, node4.in1 + node2.out0, node2.out1, node2.out2, node2.out3 -> node0.in1, node1.in1, node3.in2, node4.in2 + node3.out0, node3.out1, node3.out2, node3.out3 -> node0.in2, node1.in2, node2.in2, node4.in3 + node4.out0, node4.out1, node4.out2, node4.out3 -> node0.in3, node1.in3, node2.in3, node3.in3 } diff --git a/experiments/Python/src/GeneralModels/tester.lf b/experiments/Python/src/GeneralModels/tester.lf index 70a50364..986439df 100644 --- a/experiments/Python/src/GeneralModels/tester.lf +++ b/experiments/Python/src/GeneralModels/tester.lf @@ -1,15 +1,13 @@ -/* Neural Net */ +/** Neural Net */ +target Python -target Python {}; +preamble {= =} -preamble {==} +### Input Setup Reactor +reactor Setup { + state _function -### Input Setup Reactor -reactor Setup { - - state _function - - input results + input results output out0 output out1 @@ -28,16 +26,15 @@ reactor Setup { output out14 output out15 output intended - reaction(startup) {==} - -} -### Input Layer -reactor Input { + reaction(startup) {= =} +} - state _reading - state _weight - state _bias +### Input Layer +reactor Input { + state _reading + state _weight + state _bias input data input back_in0 @@ -47,6 +44,7 @@ reactor Input { input back_in4 input back_in5 input back_in6 + output out0 output out1 output out2 @@ -55,135 +53,148 @@ reactor Input { output out5 output out6 - initial mode forward_prop { - - reaction(startup) {==} - - reaction(data) -> out0, out1, out2, out3, out4, out5, out6 {==} - - } mode back_prop { + initial mode forward_prop { + reaction(startup) {= =} - reaction(back_in0, back_in1, back_in2, back_in3, back_in4, back_in5, back_in6) {==} + reaction(data) -> out0, out1, out2, out3, out4, out5, out6 {= =} + } + mode back_prop { + reaction(back_in0, back_in1, back_in2, back_in3, back_in4, back_in5, back_in6) {= =} } -} +} ### Hidden Layer 0 -reactor HL0 { - - state _reading - state _weight - state _bias +reactor HL0 { + state _reading + state _weight + state _bias input in0 - output back_out0 input in1 - output back_out1 input in2 - output back_out2 input in3 - output back_out3 input in4 - output back_out4 input in5 - output back_out5 input in6 - output back_out6 input in7 - output back_out7 input in8 - output back_out8 input in9 - output back_out9 input in10 - output back_out10 input in11 - output back_out11 input in12 - output back_out12 input in13 - output back_out13 input in14 - output back_out14 input in15 - output back_out15 - output out0 input back_in0 - output out1 input back_in1 - output out2 input back_in2 - output out3 input back_in3 - output out4 input back_in4 - output out5 input back_in5 - output out6 input back_in6 - initial mode forward_prop { - - reaction(startup) {==} - - reaction(in0, in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11, in12, in13, in14, in15) -> out0, out1, out2, out3, out4, out5, out6 {==} + output out0 + output out1 + output out2 + output out3 + output out4 + output out5 + output out6 + output back_out0 + output back_out1 + output back_out2 + output back_out3 + output back_out4 + output back_out5 + output back_out6 + output back_out7 + output back_out8 + output back_out9 + output back_out10 + output back_out11 + output back_out12 + output back_out13 + output back_out14 + output back_out15 - } mode back_prop { + initial mode forward_prop { + reaction(startup) {= =} + + reaction( + in0, + in1, + in2, + in3, + in4, + in5, + in6, + in7, + in8, + in9, + in10, + in11, + in12, + in13, + in14, + in15) -> out0, out1, out2, out3, out4, out5, out6 {= =} + } - reaction(back_in0, back_in1, back_in2, back_in3, back_in4, back_in5, back_in6) -> back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6, back_out7, back_out8, back_out9, back_out10, back_out11, back_out12, back_out13, back_out14, back_out15 {==} + mode back_prop { + reaction(back_in0, back_in1, back_in2, back_in3, back_in4, back_in5, back_in6) -> + back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6, back_out7, back_out8, back_out9, back_out10, back_out11, back_out12, back_out13, back_out14, back_out15 {= - } + =} + } } ### Hidden Layer 1 -reactor HL1 { - - state _reading - state _weight - state _bias +reactor HL1 { + state _reading + state _weight + state _bias input in0 - output back_out0 input in1 - output back_out1 input in2 - output back_out2 input in3 - output back_out3 input in4 - output back_out4 input in5 - output back_out5 input in6 - output back_out6 - output out0 input back_in0 - output out1 input back_in1 - output out2 input back_in2 - output out3 input back_in3 - initial mode forward_prop { - - reaction(startup) {==} - - reaction(in0, in1, in2, in3, in4, in5, in6) -> out0, out1, out2, out3 {==} + output out0 + output out1 + output out2 + output out3 + output back_out0 + output back_out1 + output back_out2 + output back_out3 + output back_out4 + output back_out5 + output back_out6 - } mode back_prop { + initial mode forward_prop { + reaction(startup) {= =} - reaction(back_in0, back_in1, back_in2, back_in3) -> back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6 {==} + reaction(in0, in1, in2, in3, in4, in5, in6) -> out0, out1, out2, out3 {= =} + } - } + mode back_prop { + reaction(back_in0, back_in1, back_in2, back_in3) -> + back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6 {= =} + } } ### Output Layer reactor Output { - - state _reading - state _weight - state _bias + state _reading + state _weight + state _bias input in0 input in1 @@ -204,65 +215,61 @@ reactor Output { output back_out6 initial mode forward_prop { + reaction(in0, in1, in2, in3, in4, in5, in6) -> result {= =} + } - reaction(in0, in1, in2, in3, in4, in5, in6) -> result {==} - - } mode back_prop { - - reaction(back_in) -> back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6 {==} - - } + mode back_prop { + reaction(back_in) -> + back_out0, back_out1, back_out2, back_out3, back_out4, back_out5, back_out6 {= =} + } } ### Reactor Processor reactor Processor { + state _processing -state _processing - -input in0 -input in1 -input in2 -input in3 -input intended + input in0 + input in1 + input in2 + input in3 + input intended -output back_out0 -output back_out1 -output back_out2 -output back_out3 -output resultToSystem + output back_out0 + output back_out1 + output back_out2 + output back_out3 + output resultToSystem initial mode forward_prop { + reaction(in0, in1, in2, in3) -> resultToSystem {= =} + } - reaction(in0, in1, in2, in3) -> resultToSystem {==} - - } mode back_prop { - - reaction(startup) {==} - + mode back_prop { + reaction(startup) {= =} } } ### Main Reactor main reactor { - setup = new Setup() - input0= new Input() - input1= new Input() - input2= new Input() - input3= new Input() - input4= new Input() - input5= new Input() - input6= new Input() - input7= new Input() - input8= new Input() - input9= new Input() - input10= new Input() - input11= new Input() - input12= new Input() - input13= new Input() - input14= new Input() - input15= new Input() + input0 = new Input() + input1 = new Input() + input2 = new Input() + input3 = new Input() + input4 = new Input() + input5 = new Input() + input6 = new Input() + input7 = new Input() + input8 = new Input() + input9 = new Input() + input10 = new Input() + input11 = new Input() + input12 = new Input() + input13 = new Input() + input14 = new Input() + input15 = new Input() + hl0_0 = new HL0() hl0_1 = new HL0() hl0_2 = new HL0() @@ -270,6 +277,7 @@ main reactor { hl0_4 = new HL0() hl0_5 = new HL0() hl0_6 = new HL0() + hl1_0 = new HL1() hl1_1 = new HL1() hl1_2 = new HL1() @@ -277,12 +285,10 @@ main reactor { hl1_4 = new HL1() hl1_5 = new HL1() hl1_6 = new HL1() - output0 = new Output() + output0 = new Output() output1 = new Output() - output2 = new Output() - output3 = new Output() processor = new Processor() @@ -303,70 +309,430 @@ main reactor { setup.out13 -> input13.data setup.out14 -> input14.data setup.out15 -> input15.data - input0.out0, input1.out0, input2.out0, input3.out0, input4.out0, input5.out0, input6.out0, input7.out0, input8.out0, input9.out0, input10.out0, input11.out0, input12.out0, input13.out0, input14.out0, input15.out0 -> hl0_0.in0, hl0_0.in1, hl0_0.in2, hl0_0.in3, hl0_0.in4, hl0_0.in5, hl0_0.in6, hl0_0.in7, hl0_0.in8, hl0_0.in9, hl0_0.in10, hl0_0.in11, hl0_0.in12, hl0_0.in13, hl0_0.in14, hl0_0.in15 - input0.out1, input1.out1, input2.out1, input3.out1, input4.out1, input5.out1, input6.out1, input7.out1, input8.out1, input9.out1, input10.out1, input11.out1, input12.out1, input13.out1, input14.out1, input15.out1 -> hl0_1.in0, hl0_1.in1, hl0_1.in2, hl0_1.in3, hl0_1.in4, hl0_1.in5, hl0_1.in6, hl0_1.in7, hl0_1.in8, hl0_1.in9, hl0_1.in10, hl0_1.in11, hl0_1.in12, hl0_1.in13, hl0_1.in14, hl0_1.in15 - input0.out2, input1.out2, input2.out2, input3.out2, input4.out2, input5.out2, input6.out2, input7.out2, input8.out2, input9.out2, input10.out2, input11.out2, input12.out2, input13.out2, input14.out2, input15.out2 -> hl0_2.in0, hl0_2.in1, hl0_2.in2, hl0_2.in3, hl0_2.in4, hl0_2.in5, hl0_2.in6, hl0_2.in7, hl0_2.in8, hl0_2.in9, hl0_2.in10, hl0_2.in11, hl0_2.in12, hl0_2.in13, hl0_2.in14, hl0_2.in15 - input0.out3, input1.out3, input2.out3, input3.out3, input4.out3, input5.out3, input6.out3, input7.out3, input8.out3, input9.out3, input10.out3, input11.out3, input12.out3, input13.out3, input14.out3, input15.out3 -> hl0_3.in0, hl0_3.in1, hl0_3.in2, hl0_3.in3, hl0_3.in4, hl0_3.in5, hl0_3.in6, hl0_3.in7, hl0_3.in8, hl0_3.in9, hl0_3.in10, hl0_3.in11, hl0_3.in12, hl0_3.in13, hl0_3.in14, hl0_3.in15 - input0.out4, input1.out4, input2.out4, input3.out4, input4.out4, input5.out4, input6.out4, input7.out4, input8.out4, input9.out4, input10.out4, input11.out4, input12.out4, input13.out4, input14.out4, input15.out4 -> hl0_4.in0, hl0_4.in1, hl0_4.in2, hl0_4.in3, hl0_4.in4, hl0_4.in5, hl0_4.in6, hl0_4.in7, hl0_4.in8, hl0_4.in9, hl0_4.in10, hl0_4.in11, hl0_4.in12, hl0_4.in13, hl0_4.in14, hl0_4.in15 - input0.out5, input1.out5, input2.out5, input3.out5, input4.out5, input5.out5, input6.out5, input7.out5, input8.out5, input9.out5, input10.out5, input11.out5, input12.out5, input13.out5, input14.out5, input15.out5 -> hl0_5.in0, hl0_5.in1, hl0_5.in2, hl0_5.in3, hl0_5.in4, hl0_5.in5, hl0_5.in6, hl0_5.in7, hl0_5.in8, hl0_5.in9, hl0_5.in10, hl0_5.in11, hl0_5.in12, hl0_5.in13, hl0_5.in14, hl0_5.in15 - input0.out6, input1.out6, input2.out6, input3.out6, input4.out6, input5.out6, input6.out6, input7.out6, input8.out6, input9.out6, input10.out6, input11.out6, input12.out6, input13.out6, input14.out6, input15.out6 -> hl0_6.in0, hl0_6.in1, hl0_6.in2, hl0_6.in3, hl0_6.in4, hl0_6.in5, hl0_6.in6, hl0_6.in7, hl0_6.in8, hl0_6.in9, hl0_6.in10, hl0_6.in11, hl0_6.in12, hl0_6.in13, hl0_6.in14, hl0_6.in15 - hl0_0.out0, hl0_1.out0, hl0_2.out0, hl0_3.out0, hl0_4.out0, hl0_5.out0, hl0_6.out0 -> hl1_0.in0, hl1_0.in1, hl1_0.in2, hl1_0.in3, hl1_0.in4, hl1_0.in5, hl1_0.in6 - hl0_0.out1, hl0_1.out1, hl0_2.out1, hl0_3.out1, hl0_4.out1, hl0_5.out1, hl0_6.out1 -> hl1_1.in0, hl1_1.in1, hl1_1.in2, hl1_1.in3, hl1_1.in4, hl1_1.in5, hl1_1.in6 - hl0_0.out2, hl0_1.out2, hl0_2.out2, hl0_3.out2, hl0_4.out2, hl0_5.out2, hl0_6.out2 -> hl1_2.in0, hl1_2.in1, hl1_2.in2, hl1_2.in3, hl1_2.in4, hl1_2.in5, hl1_2.in6 - hl0_0.out3, hl0_1.out3, hl0_2.out3, hl0_3.out3, hl0_4.out3, hl0_5.out3, hl0_6.out3 -> hl1_3.in0, hl1_3.in1, hl1_3.in2, hl1_3.in3, hl1_3.in4, hl1_3.in5, hl1_3.in6 - hl0_0.out4, hl0_1.out4, hl0_2.out4, hl0_3.out4, hl0_4.out4, hl0_5.out4, hl0_6.out4 -> hl1_4.in0, hl1_4.in1, hl1_4.in2, hl1_4.in3, hl1_4.in4, hl1_4.in5, hl1_4.in6 - hl0_0.out5, hl0_1.out5, hl0_2.out5, hl0_3.out5, hl0_4.out5, hl0_5.out5, hl0_6.out5 -> hl1_5.in0, hl1_5.in1, hl1_5.in2, hl1_5.in3, hl1_5.in4, hl1_5.in5, hl1_5.in6 - hl0_0.out6, hl0_1.out6, hl0_2.out6, hl0_3.out6, hl0_4.out6, hl0_5.out6, hl0_6.out6 -> hl1_6.in0, hl1_6.in1, hl1_6.in2, hl1_6.in3, hl1_6.in4, hl1_6.in5, hl1_6.in6 - hl1_0.out0, hl1_1.out0, hl1_2.out0, hl1_3.out0, hl1_4.out0, hl1_5.out0, hl1_6.out0 -> output0.in0, output0.in1, output0.in2, output0.in3, output0.in4, output0.in5, output0.in6 - hl1_0.out1, hl1_1.out1, hl1_2.out1, hl1_3.out1, hl1_4.out1, hl1_5.out1, hl1_6.out1 -> output1.in0, output1.in1, output1.in2, output1.in3, output1.in4, output1.in5, output1.in6 - hl1_0.out2, hl1_1.out2, hl1_2.out2, hl1_3.out2, hl1_4.out2, hl1_5.out2, hl1_6.out2 -> output2.in0, output2.in1, output2.in2, output2.in3, output2.in4, output2.in5, output2.in6 - hl1_0.out3, hl1_1.out3, hl1_2.out3, hl1_3.out3, hl1_4.out3, hl1_5.out3, hl1_6.out3 -> output3.in0, output3.in1, output3.in2, output3.in3, output3.in4, output3.in5, output3.in6 + + input0.out0 -> hl0_0.in0 + input1.out0 -> hl0_0.in1 + input2.out0 -> hl0_0.in2 + input3.out0 -> hl0_0.in3 + input4.out0 -> hl0_0.in4 + input5.out0 -> hl0_0.in5 + input6.out0 -> hl0_0.in6 + input7.out0 -> hl0_0.in7 + input8.out0 -> hl0_0.in8 + input9.out0 -> hl0_0.in9 + input10.out0 -> hl0_0.in10 + input11.out0 -> hl0_0.in11 + input12.out0 -> hl0_0.in12 + input13.out0 -> hl0_0.in13 + input14.out0 -> hl0_0.in14 + input15.out0 -> hl0_0.in15 + + input0.out1 -> hl0_1.in0 + input1.out1 -> hl0_1.in1 + input2.out1 -> hl0_1.in2 + input3.out1 -> hl0_1.in3 + input4.out1 -> hl0_1.in4 + input5.out1 -> hl0_1.in5 + input6.out1 -> hl0_1.in6 + input7.out1 -> hl0_1.in7 + input8.out1 -> hl0_1.in8 + input9.out1 -> hl0_1.in9 + input10.out1 -> hl0_1.in10 + input11.out1 -> hl0_1.in11 + input12.out1 -> hl0_1.in12 + input13.out1 -> hl0_1.in13 + input14.out1 -> hl0_1.in14 + input15.out1 -> hl0_1.in15 + + input0.out2 -> hl0_2.in0 + input1.out2 -> hl0_2.in1 + input2.out2 -> hl0_2.in2 + input3.out2 -> hl0_2.in3 + input4.out2 -> hl0_2.in4 + input5.out2 -> hl0_2.in5 + input6.out2 -> hl0_2.in6 + input7.out2 -> hl0_2.in7 + input8.out2 -> hl0_2.in8 + input9.out2 -> hl0_2.in9 + input10.out2 -> hl0_2.in10 + input11.out2 -> hl0_2.in11 + input12.out2 -> hl0_2.in12 + input13.out2 -> hl0_2.in13 + input14.out2 -> hl0_2.in14 + input15.out2 -> hl0_2.in15 + + input0.out3 -> hl0_3.in0 + input1.out3 -> hl0_3.in1 + input2.out3 -> hl0_3.in2 + input3.out3 -> hl0_3.in3 + input4.out3 -> hl0_3.in4 + input5.out3 -> hl0_3.in5 + input6.out3 -> hl0_3.in6 + input7.out3 -> hl0_3.in7 + input8.out3 -> hl0_3.in8 + input9.out3 -> hl0_3.in9 + input10.out3 -> hl0_3.in10 + input11.out3 -> hl0_3.in11 + input12.out3 -> hl0_3.in12 + input13.out3 -> hl0_3.in13 + input14.out3 -> hl0_3.in14 + input15.out3 -> hl0_3.in15 + + input0.out4 -> hl0_4.in0 + input1.out4 -> hl0_4.in1 + input2.out4 -> hl0_4.in2 + input3.out4 -> hl0_4.in3 + input4.out4 -> hl0_4.in4 + input5.out4 -> hl0_4.in5 + input6.out4 -> hl0_4.in6 + input7.out4 -> hl0_4.in7 + input8.out4 -> hl0_4.in8 + input9.out4 -> hl0_4.in9 + input10.out4 -> hl0_4.in10 + input11.out4 -> hl0_4.in11 + input12.out4 -> hl0_4.in12 + input13.out4 -> hl0_4.in13 + input14.out4 -> hl0_4.in14 + input15.out4 -> hl0_4.in15 + + input0.out5 -> hl0_5.in0 + input1.out5 -> hl0_5.in1 + input2.out5 -> hl0_5.in2 + input3.out5 -> hl0_5.in3 + input4.out5 -> hl0_5.in4 + input5.out5 -> hl0_5.in5 + input6.out5 -> hl0_5.in6 + input7.out5 -> hl0_5.in7 + input8.out5 -> hl0_5.in8 + input9.out5 -> hl0_5.in9 + input10.out5 -> hl0_5.in10 + input11.out5 -> hl0_5.in11 + input12.out5 -> hl0_5.in12 + input13.out5 -> hl0_5.in13 + input14.out5 -> hl0_5.in14 + input15.out5 -> hl0_5.in15 + + input0.out6 -> hl0_6.in0 + input1.out6 -> hl0_6.in1 + input2.out6 -> hl0_6.in2 + input3.out6 -> hl0_6.in3 + input4.out6 -> hl0_6.in4 + input5.out6 -> hl0_6.in5 + input6.out6 -> hl0_6.in6 + input7.out6 -> hl0_6.in7 + input8.out6 -> hl0_6.in8 + input9.out6 -> hl0_6.in9 + input10.out6 -> hl0_6.in10 + input11.out6 -> hl0_6.in11 + input12.out6 -> hl0_6.in12 + input13.out6 -> hl0_6.in13 + input14.out6 -> hl0_6.in14 + input15.out6 -> hl0_6.in15 + + hl0_0.out0 -> hl1_0.in0 + hl0_1.out0 -> hl1_0.in1 + hl0_2.out0 -> hl1_0.in2 + hl0_3.out0 -> hl1_0.in3 + hl0_4.out0 -> hl1_0.in4 + hl0_5.out0 -> hl1_0.in5 + hl0_6.out0 -> hl1_0.in6 + + hl0_0.out1 -> hl1_1.in0 + hl0_1.out1 -> hl1_1.in1 + hl0_2.out1 -> hl1_1.in2 + hl0_3.out1 -> hl1_1.in3 + hl0_4.out1 -> hl1_1.in4 + hl0_5.out1 -> hl1_1.in5 + hl0_6.out1 -> hl1_1.in6 + + hl0_0.out2 -> hl1_2.in0 + hl0_1.out2 -> hl1_2.in1 + hl0_2.out2 -> hl1_2.in2 + hl0_3.out2 -> hl1_2.in3 + hl0_4.out2 -> hl1_2.in4 + hl0_5.out2 -> hl1_2.in5 + hl0_6.out2 -> hl1_2.in6 + + hl0_0.out3 -> hl1_3.in0 + hl0_1.out3 -> hl1_3.in1 + hl0_2.out3 -> hl1_3.in2 + hl0_3.out3 -> hl1_3.in3 + hl0_4.out3 -> hl1_3.in4 + hl0_5.out3 -> hl1_3.in5 + hl0_6.out3 -> hl1_3.in6 + + hl0_0.out4 -> hl1_4.in0 + hl0_1.out4 -> hl1_4.in1 + hl0_2.out4 -> hl1_4.in2 + hl0_3.out4 -> hl1_4.in3 + hl0_4.out4 -> hl1_4.in4 + hl0_5.out4 -> hl1_4.in5 + hl0_6.out4 -> hl1_4.in6 + + hl0_0.out5 -> hl1_5.in0 + hl0_1.out5 -> hl1_5.in1 + hl0_2.out5 -> hl1_5.in2 + hl0_3.out5 -> hl1_5.in3 + hl0_4.out5 -> hl1_5.in4 + hl0_5.out5 -> hl1_5.in5 + hl0_6.out5 -> hl1_5.in6 + + hl0_0.out6 -> hl1_6.in0 + hl0_1.out6 -> hl1_6.in1 + hl0_2.out6 -> hl1_6.in2 + hl0_3.out6 -> hl1_6.in3 + hl0_4.out6 -> hl1_6.in4 + hl0_5.out6 -> hl1_6.in5 + hl0_6.out6 -> hl1_6.in6 + + hl1_0.out0 -> output0.in0 + hl1_1.out0 -> output0.in1 + hl1_2.out0 -> output0.in2 + hl1_3.out0 -> output0.in3 + hl1_4.out0 -> output0.in4 + hl1_5.out0 -> output0.in5 + hl1_6.out0 -> output0.in6 + + hl1_0.out1 -> output1.in0 + hl1_1.out1 -> output1.in1 + hl1_2.out1 -> output1.in2 + hl1_3.out1 -> output1.in3 + hl1_4.out1 -> output1.in4 + hl1_5.out1 -> output1.in5 + hl1_6.out1 -> output1.in6 + + hl1_0.out2 -> output2.in0 + hl1_1.out2 -> output2.in1 + hl1_2.out2 -> output2.in2 + hl1_3.out2 -> output2.in3 + hl1_4.out2 -> output2.in4 + hl1_5.out2 -> output2.in5 + hl1_6.out2 -> output2.in6 + + hl1_0.out3 -> output3.in0 + hl1_1.out3 -> output3.in1 + hl1_2.out3 -> output3.in2 + hl1_3.out3 -> output3.in3 + hl1_4.out3 -> output3.in4 + hl1_5.out3 -> output3.in5 + hl1_6.out3 -> output3.in6 + output0.result -> processor.in0 output1.result -> processor.in1 output2.result -> processor.in2 output3.result -> processor.in3 - + setup.intended -> processor.intended processor.back_out0 -> output0.back_in processor.back_out1 -> output1.back_in processor.back_out2 -> output2.back_in processor.back_out3 -> output3.back_in - hl0_0.back_out0, hl0_0.back_out1, hl0_0.back_out2, hl0_0.back_out3, hl0_0.back_out4, hl0_0.back_out5, hl0_0.back_out6, hl0_0.back_out7, hl0_0.back_out8, hl0_0.back_out9, hl0_0.back_out10, hl0_0.back_out11, hl0_0.back_out12, hl0_0.back_out13, hl0_0.back_out14, hl0_0.back_out15 -> input0.back_in0, input1.back_in0, input2.back_in0, input3.back_in0, input4.back_in0, input5.back_in0, input6.back_in0, input7.back_in0, input8.back_in0, input9.back_in0, input10.back_in0, input11.back_in0, input12.back_in0, input13.back_in0, input14.back_in0, input15.back_in0 - - hl0_1.back_out0, hl0_1.back_out1, hl0_1.back_out2, hl0_1.back_out3, hl0_1.back_out4, hl0_1.back_out5, hl0_1.back_out6, hl0_1.back_out7, hl0_1.back_out8, hl0_1.back_out9, hl0_1.back_out10, hl0_1.back_out11, hl0_1.back_out12, hl0_1.back_out13, hl0_1.back_out14, hl0_1.back_out15 -> input0.back_in1, input1.back_in1, input2.back_in1, input3.back_in1, input4.back_in1, input5.back_in1, input6.back_in1, input7.back_in1, input8.back_in1, input9.back_in1, input10.back_in1, input11.back_in1, input12.back_in1, input13.back_in1, input14.back_in1, input15.back_in1 - - hl0_2.back_out0, hl0_2.back_out1, hl0_2.back_out2, hl0_2.back_out3, hl0_2.back_out4, hl0_2.back_out5, hl0_2.back_out6, hl0_2.back_out7, hl0_2.back_out8, hl0_2.back_out9, hl0_2.back_out10, hl0_2.back_out11, hl0_2.back_out12, hl0_2.back_out13, hl0_2.back_out14, hl0_2.back_out15 -> input0.back_in2, input1.back_in2, input2.back_in2, input3.back_in2, input4.back_in2, input5.back_in2, input6.back_in2, input7.back_in2, input8.back_in2, input9.back_in2, input10.back_in2, input11.back_in2, input12.back_in2, input13.back_in2, input14.back_in2, input15.back_in2 - - hl0_3.back_out0, hl0_3.back_out1, hl0_3.back_out2, hl0_3.back_out3, hl0_3.back_out4, hl0_3.back_out5, hl0_3.back_out6, hl0_3.back_out7, hl0_3.back_out8, hl0_3.back_out9, hl0_3.back_out10, hl0_3.back_out11, hl0_3.back_out12, hl0_3.back_out13, hl0_3.back_out14, hl0_3.back_out15 -> input0.back_in3, input1.back_in3, input2.back_in3, input3.back_in3, input4.back_in3, input5.back_in3, input6.back_in3, input7.back_in3, input8.back_in3, input9.back_in3, input10.back_in3, input11.back_in3, input12.back_in3, input13.back_in3, input14.back_in3, input15.back_in3 - - hl0_4.back_out0, hl0_4.back_out1, hl0_4.back_out2, hl0_4.back_out3, hl0_4.back_out4, hl0_4.back_out5, hl0_4.back_out6, hl0_4.back_out7, hl0_4.back_out8, hl0_4.back_out9, hl0_4.back_out10, hl0_4.back_out11, hl0_4.back_out12, hl0_4.back_out13, hl0_4.back_out14, hl0_4.back_out15 -> input0.back_in4, input1.back_in4, input2.back_in4, input3.back_in4, input4.back_in4, input5.back_in4, input6.back_in4, input7.back_in4, input8.back_in4, input9.back_in4, input10.back_in4, input11.back_in4, input12.back_in4, input13.back_in4, input14.back_in4, input15.back_in4 - - hl0_5.back_out0, hl0_5.back_out1, hl0_5.back_out2, hl0_5.back_out3, hl0_5.back_out4, hl0_5.back_out5, hl0_5.back_out6, hl0_5.back_out7, hl0_5.back_out8, hl0_5.back_out9, hl0_5.back_out10, hl0_5.back_out11, hl0_5.back_out12, hl0_5.back_out13, hl0_5.back_out14, hl0_5.back_out15 -> input0.back_in5, input1.back_in5, input2.back_in5, input3.back_in5, input4.back_in5, input5.back_in5, input6.back_in5, input7.back_in5, input8.back_in5, input9.back_in5, input10.back_in5, input11.back_in5, input12.back_in5, input13.back_in5, input14.back_in5, input15.back_in5 - - hl0_6.back_out0, hl0_6.back_out1, hl0_6.back_out2, hl0_6.back_out3, hl0_6.back_out4, hl0_6.back_out5, hl0_6.back_out6, hl0_6.back_out7, hl0_6.back_out8, hl0_6.back_out9, hl0_6.back_out10, hl0_6.back_out11, hl0_6.back_out12, hl0_6.back_out13, hl0_6.back_out14, hl0_6.back_out15 -> input0.back_in6, input1.back_in6, input2.back_in6, input3.back_in6, input4.back_in6, input5.back_in6, input6.back_in6, input7.back_in6, input8.back_in6, input9.back_in6, input10.back_in6, input11.back_in6, input12.back_in6, input13.back_in6, input14.back_in6, input15.back_in6 - - hl1_0.back_out0, hl1_0.back_out1, hl1_0.back_out2, hl1_0.back_out3, hl1_0.back_out4, hl1_0.back_out5, hl1_0.back_out6 -> hl0_0.back_in0, hl0_1.back_in0, hl0_2.back_in0, hl0_3.back_in0, hl0_4.back_in0, hl0_5.back_in0, hl0_6.back_in0 - - hl1_1.back_out0, hl1_1.back_out1, hl1_1.back_out2, hl1_1.back_out3, hl1_1.back_out4, hl1_1.back_out5, hl1_1.back_out6 -> hl0_0.back_in1, hl0_1.back_in1, hl0_2.back_in1, hl0_3.back_in1, hl0_4.back_in1, hl0_5.back_in1, hl0_6.back_in1 - - hl1_2.back_out0, hl1_2.back_out1, hl1_2.back_out2, hl1_2.back_out3, hl1_2.back_out4, hl1_2.back_out5, hl1_2.back_out6 -> hl0_0.back_in2, hl0_1.back_in2, hl0_2.back_in2, hl0_3.back_in2, hl0_4.back_in2, hl0_5.back_in2, hl0_6.back_in2 - - hl1_3.back_out0, hl1_3.back_out1, hl1_3.back_out2, hl1_3.back_out3, hl1_3.back_out4, hl1_3.back_out5, hl1_3.back_out6 -> hl0_0.back_in3, hl0_1.back_in3, hl0_2.back_in3, hl0_3.back_in3, hl0_4.back_in3, hl0_5.back_in3, hl0_6.back_in3 - - hl1_4.back_out0, hl1_4.back_out1, hl1_4.back_out2, hl1_4.back_out3, hl1_4.back_out4, hl1_4.back_out5, hl1_4.back_out6 -> hl0_0.back_in4, hl0_1.back_in4, hl0_2.back_in4, hl0_3.back_in4, hl0_4.back_in4, hl0_5.back_in4, hl0_6.back_in4 - - hl1_5.back_out0, hl1_5.back_out1, hl1_5.back_out2, hl1_5.back_out3, hl1_5.back_out4, hl1_5.back_out5, hl1_5.back_out6 -> hl0_0.back_in5, hl0_1.back_in5, hl0_2.back_in5, hl0_3.back_in5, hl0_4.back_in5, hl0_5.back_in5, hl0_6.back_in5 - - hl1_6.back_out0, hl1_6.back_out1, hl1_6.back_out2, hl1_6.back_out3, hl1_6.back_out4, hl1_6.back_out5, hl1_6.back_out6 -> hl0_0.back_in6, hl0_1.back_in6, hl0_2.back_in6, hl0_3.back_in6, hl0_4.back_in6, hl0_5.back_in6, hl0_6.back_in6 - - output0.back_out0, output0.back_out1, output0.back_out2, output0.back_out3, output0.back_out4, output0.back_out5, output0.back_out6 -> hl1_0.back_in0, hl1_1.back_in0, hl1_2.back_in0, hl1_3.back_in0, hl1_4.back_in0, hl1_5.back_in0, hl1_6.back_in0 - - output1.back_out0, output1.back_out1, output1.back_out2, output1.back_out3, output1.back_out4, output1.back_out5, output1.back_out6 -> hl1_0.back_in1, hl1_1.back_in1, hl1_2.back_in1, hl1_3.back_in1, hl1_4.back_in1, hl1_5.back_in1, hl1_6.back_in1 - - output2.back_out0, output2.back_out1, output2.back_out2, output2.back_out3, output2.back_out4, output2.back_out5, output2.back_out6 -> hl1_0.back_in2, hl1_1.back_in2, hl1_2.back_in2, hl1_3.back_in2, hl1_4.back_in2, hl1_5.back_in2, hl1_6.back_in2 - - output3.back_out0, output3.back_out1, output3.back_out2, output3.back_out3, output3.back_out4, output3.back_out5, output3.back_out6 -> hl1_0.back_in3, hl1_1.back_in3, hl1_2.back_in3, hl1_3.back_in3, hl1_4.back_in3, hl1_5.back_in3, hl1_6.back_in3 - + hl0_0.back_out0 -> input0.back_in0 + hl0_0.back_out1 -> input1.back_in0 + hl0_0.back_out2 -> input2.back_in0 + hl0_0.back_out3 -> input3.back_in0 + hl0_0.back_out4 -> input4.back_in0 + hl0_0.back_out5 -> input5.back_in0 + hl0_0.back_out6 -> input6.back_in0 + hl0_0.back_out7 -> input7.back_in0 + hl0_0.back_out8 -> input8.back_in0 + hl0_0.back_out9 -> input9.back_in0 + hl0_0.back_out10 -> input10.back_in0 + hl0_0.back_out11 -> input11.back_in0 + hl0_0.back_out12 -> input12.back_in0 + hl0_0.back_out13 -> input13.back_in0 + hl0_0.back_out14 -> input14.back_in0 + hl0_0.back_out15 -> input15.back_in0 + + hl0_1.back_out0 -> input0.back_in1 + hl0_1.back_out1 -> input1.back_in1 + hl0_1.back_out2 -> input2.back_in1 + hl0_1.back_out3 -> input3.back_in1 + hl0_1.back_out4 -> input4.back_in1 + hl0_1.back_out5 -> input5.back_in1 + hl0_1.back_out6 -> input6.back_in1 + hl0_1.back_out7 -> input7.back_in1 + hl0_1.back_out8 -> input8.back_in1 + hl0_1.back_out9 -> input9.back_in1 + hl0_1.back_out10 -> input10.back_in1 + hl0_1.back_out11 -> input11.back_in1 + hl0_1.back_out12 -> input12.back_in1 + hl0_1.back_out13 -> input13.back_in1 + hl0_1.back_out14 -> input14.back_in1 + hl0_1.back_out15 -> input15.back_in1 + + hl0_2.back_out0 -> input0.back_in2 + hl0_2.back_out1 -> input1.back_in2 + hl0_2.back_out2 -> input2.back_in2 + hl0_2.back_out3 -> input3.back_in2 + hl0_2.back_out4 -> input4.back_in2 + hl0_2.back_out5 -> input5.back_in2 + hl0_2.back_out6 -> input6.back_in2 + hl0_2.back_out7 -> input7.back_in2 + hl0_2.back_out8 -> input8.back_in2 + hl0_2.back_out9 -> input9.back_in2 + hl0_2.back_out10 -> input10.back_in2 + hl0_2.back_out11 -> input11.back_in2 + hl0_2.back_out12 -> input12.back_in2 + hl0_2.back_out13 -> input13.back_in2 + hl0_2.back_out14 -> input14.back_in2 + hl0_2.back_out15 -> input15.back_in2 + + hl0_3.back_out0 -> input0.back_in3 + hl0_3.back_out1 -> input1.back_in3 + hl0_3.back_out2 -> input2.back_in3 + hl0_3.back_out3 -> input3.back_in3 + hl0_3.back_out4 -> input4.back_in3 + hl0_3.back_out5 -> input5.back_in3 + hl0_3.back_out6 -> input6.back_in3 + hl0_3.back_out7 -> input7.back_in3 + hl0_3.back_out8 -> input8.back_in3 + hl0_3.back_out9 -> input9.back_in3 + hl0_3.back_out10 -> input10.back_in3 + hl0_3.back_out11 -> input11.back_in3 + hl0_3.back_out12 -> input12.back_in3 + hl0_3.back_out13 -> input13.back_in3 + hl0_3.back_out14 -> input14.back_in3 + hl0_3.back_out15 -> input15.back_in3 + + hl0_4.back_out0 -> input0.back_in4 + hl0_4.back_out1 -> input1.back_in4 + hl0_4.back_out2 -> input2.back_in4 + hl0_4.back_out3 -> input3.back_in4 + hl0_4.back_out4 -> input4.back_in4 + hl0_4.back_out5 -> input5.back_in4 + hl0_4.back_out6 -> input6.back_in4 + hl0_4.back_out7 -> input7.back_in4 + hl0_4.back_out8 -> input8.back_in4 + hl0_4.back_out9 -> input9.back_in4 + hl0_4.back_out10 -> input10.back_in4 + hl0_4.back_out11 -> input11.back_in4 + hl0_4.back_out12 -> input12.back_in4 + hl0_4.back_out13 -> input13.back_in4 + hl0_4.back_out14 -> input14.back_in4 + hl0_4.back_out15 -> input15.back_in4 + + hl0_5.back_out0 -> input0.back_in5 + hl0_5.back_out1 -> input1.back_in5 + hl0_5.back_out2 -> input2.back_in5 + hl0_5.back_out3 -> input3.back_in5 + hl0_5.back_out4 -> input4.back_in5 + hl0_5.back_out5 -> input5.back_in5 + hl0_5.back_out6 -> input6.back_in5 + hl0_5.back_out7 -> input7.back_in5 + hl0_5.back_out8 -> input8.back_in5 + hl0_5.back_out9 -> input9.back_in5 + hl0_5.back_out10 -> input10.back_in5 + hl0_5.back_out11 -> input11.back_in5 + hl0_5.back_out12 -> input12.back_in5 + hl0_5.back_out13 -> input13.back_in5 + hl0_5.back_out14 -> input14.back_in5 + hl0_5.back_out15 -> input15.back_in5 + + hl0_6.back_out0 -> input0.back_in6 + hl0_6.back_out1 -> input1.back_in6 + hl0_6.back_out2 -> input2.back_in6 + hl0_6.back_out3 -> input3.back_in6 + hl0_6.back_out4 -> input4.back_in6 + hl0_6.back_out5 -> input5.back_in6 + hl0_6.back_out6 -> input6.back_in6 + hl0_6.back_out7 -> input7.back_in6 + hl0_6.back_out8 -> input8.back_in6 + hl0_6.back_out9 -> input9.back_in6 + hl0_6.back_out10 -> input10.back_in6 + hl0_6.back_out11 -> input11.back_in6 + hl0_6.back_out12 -> input12.back_in6 + hl0_6.back_out13 -> input13.back_in6 + hl0_6.back_out14 -> input14.back_in6 + hl0_6.back_out15 -> input15.back_in6 + + hl1_0.back_out0 -> hl0_0.back_in0 + hl1_0.back_out1 -> hl0_1.back_in0 + hl1_0.back_out2 -> hl0_2.back_in0 + hl1_0.back_out3 -> hl0_3.back_in0 + hl1_0.back_out4 -> hl0_4.back_in0 + hl1_0.back_out5 -> hl0_5.back_in0 + hl1_0.back_out6 -> hl0_6.back_in0 + + hl1_1.back_out0 -> hl0_0.back_in1 + hl1_1.back_out1 -> hl0_1.back_in1 + hl1_1.back_out2 -> hl0_2.back_in1 + hl1_1.back_out3 -> hl0_3.back_in1 + hl1_1.back_out4 -> hl0_4.back_in1 + hl1_1.back_out5 -> hl0_5.back_in1 + hl1_1.back_out6 -> hl0_6.back_in1 + + hl1_2.back_out0 -> hl0_0.back_in2 + hl1_2.back_out1 -> hl0_1.back_in2 + hl1_2.back_out2 -> hl0_2.back_in2 + hl1_2.back_out3 -> hl0_3.back_in2 + hl1_2.back_out4 -> hl0_4.back_in2 + hl1_2.back_out5 -> hl0_5.back_in2 + hl1_2.back_out6 -> hl0_6.back_in2 + + hl1_3.back_out0 -> hl0_0.back_in3 + hl1_3.back_out1 -> hl0_1.back_in3 + hl1_3.back_out2 -> hl0_2.back_in3 + hl1_3.back_out3 -> hl0_3.back_in3 + hl1_3.back_out4 -> hl0_4.back_in3 + hl1_3.back_out5 -> hl0_5.back_in3 + hl1_3.back_out6 -> hl0_6.back_in3 + + hl1_4.back_out0 -> hl0_0.back_in4 + hl1_4.back_out1 -> hl0_1.back_in4 + hl1_4.back_out2 -> hl0_2.back_in4 + hl1_4.back_out3 -> hl0_3.back_in4 + hl1_4.back_out4 -> hl0_4.back_in4 + hl1_4.back_out5 -> hl0_5.back_in4 + hl1_4.back_out6 -> hl0_6.back_in4 + + hl1_5.back_out0 -> hl0_0.back_in5 + hl1_5.back_out1 -> hl0_1.back_in5 + hl1_5.back_out2 -> hl0_2.back_in5 + hl1_5.back_out3 -> hl0_3.back_in5 + hl1_5.back_out4 -> hl0_4.back_in5 + hl1_5.back_out5 -> hl0_5.back_in5 + hl1_5.back_out6 -> hl0_6.back_in5 + + hl1_6.back_out0 -> hl0_0.back_in6 + hl1_6.back_out1 -> hl0_1.back_in6 + hl1_6.back_out2 -> hl0_2.back_in6 + hl1_6.back_out3 -> hl0_3.back_in6 + hl1_6.back_out4 -> hl0_4.back_in6 + hl1_6.back_out5 -> hl0_5.back_in6 + hl1_6.back_out6 -> hl0_6.back_in6 + + output0.back_out0 -> hl1_0.back_in0 + output0.back_out1 -> hl1_1.back_in0 + output0.back_out2 -> hl1_2.back_in0 + output0.back_out3 -> hl1_3.back_in0 + output0.back_out4 -> hl1_4.back_in0 + output0.back_out5 -> hl1_5.back_in0 + output0.back_out6 -> hl1_6.back_in0 + + output1.back_out0 -> hl1_0.back_in1 + output1.back_out1 -> hl1_1.back_in1 + output1.back_out2 -> hl1_2.back_in1 + output1.back_out3 -> hl1_3.back_in1 + output1.back_out4 -> hl1_4.back_in1 + output1.back_out5 -> hl1_5.back_in1 + output1.back_out6 -> hl1_6.back_in1 + + output2.back_out0 -> hl1_0.back_in2 + output2.back_out1 -> hl1_1.back_in2 + output2.back_out2 -> hl1_2.back_in2 + output2.back_out3 -> hl1_3.back_in2 + output2.back_out4 -> hl1_4.back_in2 + output2.back_out5 -> hl1_5.back_in2 + output2.back_out6 -> hl1_6.back_in2 + + output3.back_out0 -> hl1_0.back_in3 + output3.back_out1 -> hl1_1.back_in3 + output3.back_out2 -> hl1_2.back_in3 + output3.back_out3 -> hl1_3.back_in3 + output3.back_out4 -> hl1_4.back_in3 + output3.back_out5 -> hl1_5.back_in3 + output3.back_out6 -> hl1_6.back_in3 } diff --git a/experiments/Python/src/Intersection/Carla/CarlaIntersection.lf b/experiments/Python/src/Intersection/Carla/CarlaIntersection.lf index 1f3a0e66..35927b8e 100644 --- a/experiments/Python/src/Intersection/Carla/CarlaIntersection.lf +++ b/experiments/Python/src/Intersection/Carla/CarlaIntersection.lf @@ -1,91 +1,82 @@ /** * Simulate an intersection scenario using Carla. - * - * To run this example, install Carla. Then, use - * the provided bash script (if applicable) to run Carla. - * + * + * To run this example, install Carla. Then, use the provided bash script (if applicable) to run + * Carla. + * * Compile the LF program using lfc: - * - * ` lfc CarlaIntersection.lf - * + * + * `lfc CarlaIntersection.lf` + * * Locate the generated CarlaIntersection.py and run it: - * - * python3 ../../../src-gen/Intersection/Carla/CarlaIntersection/CarlaIntersection.py * - * **Note:** This example assumes that Carla is installed in - * '/opt/carla-simulator'. Please change CARLA_INSTALL_DIR in the - * preamble to reflect your install location of the simulator. - * + * `python3 ../../../src-gen/Intersection/Carla/CarlaIntersection/CarlaIntersection.py` + * + * **Note:** This example assumes that Carla is installed in '/opt/carla-simulator'. Please change + * CARLA_INSTALL_DIR in the preamble to reflect your install location of the simulator. */ target Python { - timeout: 40 sec, - files: [ - "ROS/carla_intersection/src/launch_parameters.py", - "ROS/carla_intersection/src/constants.py" - ] - // fast: true // You can enable fast to get a much faster simulation - // logging: DEBUG -}; + timeout: 40 sec, + # fast: true // You can enable fast to get a much faster simulation + # logging: DEBUG + files: [ + "ROS/carla_intersection/src/launch_parameters.py", + "ROS/carla_intersection/src/constants.py"] +} -import Vehicle from "Vehicle.lf"; -import RSU from "RSU.lf"; -import CarlaSim from "CarlaSim.lf"; +import Vehicle from "Vehicle.lf" +import RSU from "RSU.lf" +import CarlaSim from "CarlaSim.lf" preamble {= - from launch_parameters import SPAWN_POINTS, INITIAL_POSITIONS, INITIAL_VELOCITIES, \ - INTERSECTION_WIDTH, NOMINAL_SPEED_IN_INTERSECTION, INTERSECTION_POSITION + from launch_parameters import SPAWN_POINTS, INITIAL_POSITIONS, INITIAL_VELOCITIES, \ + INTERSECTION_WIDTH, NOMINAL_SPEED_IN_INTERSECTION, INTERSECTION_POSITION =} -reactor Relay(duration_in_seconds(0)) { - preamble {= - from time import sleep - =} - input i; - output o; - logical action t; +reactor Relay(duration_in_seconds=0) { + preamble {= + from time import sleep + =} + input i + output o + logical action t - reaction(i) -> t {= - t.schedule(self.duration_in_seconds, i.value) - =} + reaction(i) -> t {= + t.schedule(self.duration_in_seconds, i.value) + =} - reaction(t) -> o {= - o.set(t.value) - =} + reaction(t) -> o {= + o.set(t.value) + =} } -main reactor ( - num_entries(4), - spawn_points({=SPAWN_POINTS=}), - initial_velocities({=INITIAL_VELOCITIES=}), - initial_positions({=INITIAL_POSITIONS=}), - intersection_width({=INTERSECTION_WIDTH=}), - nominal_speed_in_intersection({=NOMINAL_SPEED_IN_INTERSECTION=}), - intersection_position({=INTERSECTION_POSITION=}) -) { - carla = new[num_entries] CarlaSim( - interval = 16 msec, - initial_velocities = initial_velocities, - spawn_points = spawn_points - ); - - vehicle = new[num_entries] Vehicle( - initial_velocities = initial_velocities, - initial_positions = initial_positions - ); +main reactor( + num_entries=4, + spawn_points = {= SPAWN_POINTS =}, + initial_velocities = {= INITIAL_VELOCITIES =}, + initial_positions = {= INITIAL_POSITIONS =}, + intersection_width = {= INTERSECTION_WIDTH =}, + nominal_speed_in_intersection = {= NOMINAL_SPEED_IN_INTERSECTION =}, + intersection_position = {= INTERSECTION_POSITION =}) { + carla = new[num_entries] CarlaSim( + interval = 16 msec, + initial_velocities=initial_velocities, + spawn_points=spawn_points) - rsu = new RSU( - num_entries = num_entries, - intersection_width = intersection_width, - nominal_speed_in_intersection = nominal_speed_in_intersection, - intersection_position = intersection_position - ); + vehicle = new[num_entries] Vehicle( + initial_velocities=initial_velocities, + initial_positions=initial_positions) - carla.velocity -> vehicle.velocity; - carla.position -> vehicle.position; - vehicle.control -> carla.command; + rsu = new RSU( + num_entries=num_entries, + intersection_width=intersection_width, + nominal_speed_in_intersection=nominal_speed_in_intersection, + intersection_position=intersection_position) - vehicle.request -> rsu.request; - rsu.grant -> vehicle.grant; + carla.velocity -> vehicle.velocity + carla.position -> vehicle.position + vehicle.control -> carla.command - + vehicle.request -> rsu.request + rsu.grant -> vehicle.grant } diff --git a/experiments/Python/src/Intersection/Carla/CarlaSim.lf b/experiments/Python/src/Intersection/Carla/CarlaSim.lf index 2b8c7f45..e57a84cb 100644 --- a/experiments/Python/src/Intersection/Carla/CarlaSim.lf +++ b/experiments/Python/src/Intersection/Carla/CarlaSim.lf @@ -1,85 +1,79 @@ /** - * This Lingua Franca program facilitates the simulation of a vehicle, specifically - * a Tesla Model 3, within the CARLA simulator environment. It utilizes a Python - * interface to interact with the simulator and manage the vehicle's state. Upon - * startup, the program initiates a connection to CARLA, sets up the world - * parameters, and spawns the vehicle at a specified location with an initial - * velocity. Once the world is initialized and the vehicle is spawned, the program - * continuously updates the vehicle's state at a specified interval. It also - * accepts external control commands for the vehicle's throttle and brake and - * applies them to the vehicle in the simulation. The program provides the - * vehicle's velocity and position as output, which can be used for further - * processing or analysis. + * This Lingua Franca program facilitates the simulation of a vehicle, specifically a Tesla Model 3, + * within the CARLA simulator environment. It utilizes a Python interface to interact with the + * simulator and manage the vehicle's state. Upon startup, the program initiates a connection to + * CARLA, sets up the world parameters, and spawns the vehicle at a specified location with an + * initial velocity. Once the world is initialized and the vehicle is spawned, the program + * continuously updates the vehicle's state at a specified interval. It also accepts external + * control commands for the vehicle's throttle and brake and applies them to the vehicle in the + * simulation. The program provides the vehicle's velocity and position as output, which can be used + * for further processing or analysis. */ target Python { - files: [ - "ROS/carla_intersection/src/carla_sim.py", - "ROS/carla_intersection/src/utils.py" - ] -}; + files: ["ROS/carla_intersection/src/carla_sim.py", "ROS/carla_intersection/src/utils.py"] +} preamble {= - from utils import make_coordinate, make_spawn_point - from carla_sim import CarlaSim + from utils import make_coordinate, make_spawn_point + from carla_sim import CarlaSim =} # TODO: Figure out why this warning persists | -# v +# v # WARNING: World::ApplySettings: After 30 attemps, the settings were not correctly set. Please check that everything is consistent. # -# as well as why the velocity of the vehicle shoots through the roof... which causes CARLA to crash. +# as well as why the velocity of the vehicle shoots through the roof... which causes CARLA to crash. reactor CarlaSim( - interval(16 msec), - bank_index(0), - initial_velocities({=None=}), - vehicle_type("vehicle.tesla.model3"), - spawn_points({=None=}) -) { - input command; - output velocity; - output position; - logical action world_is_ready; - state carla_sim; - timer timer_(10 msec, interval); + interval = 16 msec, + bank_index=0, + initial_velocities = {= None =}, + vehicle_type="vehicle.tesla.model3", + spawn_points = {= None =}) { + input command + output velocity + output position + logical action world_is_ready + state carla_sim + timer timer_(10 msec, interval) - preamble {= - class Logger: - def __init__(self, vehicle_id): - self.vehicle_id = vehicle_id + preamble {= + class Logger: + def __init__(self, vehicle_id): + self.vehicle_id = vehicle_id - def info(self, *args): - print(f"{lf.time.logical()} - carla_sim_{self.vehicle_id}: ", args) - =} + def info(self, *args): + print(f"{lf.time.logical()} - carla_sim_{self.vehicle_id}: ", args) + =} - reaction(startup) -> world_is_ready {= - print("initial velocity: ", self.initial_velocities[self.bank_index]) - self.carla_sim = CarlaSim(interval=self.interval, - vehicle_type=self.vehicle_type, - initial_velocity=make_coordinate(self.initial_velocities[self.bank_index]), - spawn_point=make_spawn_point(self.spawn_points[self.bank_index]), - logger=self.Logger(self.bank_index)) - self.carla_sim.connect_to_carla() - if self.bank_index == 0: - self.carla_sim.initialize_world(self.interval / SEC(1)) - world_is_ready.schedule(MSEC(5)) - =} + reaction(startup) -> world_is_ready {= + print("initial velocity: ", self.initial_velocities[self.bank_index]) + self.carla_sim = CarlaSim(interval=self.interval, + vehicle_type=self.vehicle_type, + initial_velocity=make_coordinate(self.initial_velocities[self.bank_index]), + spawn_point=make_spawn_point(self.spawn_points[self.bank_index]), + logger=self.Logger(self.bank_index)) + self.carla_sim.connect_to_carla() + if self.bank_index == 0: + self.carla_sim.initialize_world(self.interval / SEC(1)) + world_is_ready.schedule(MSEC(5)) + =} - reaction(world_is_ready) {= - self.carla_sim.get_world() - self.carla_sim.initialize_vehicle() - =} + reaction(world_is_ready) {= + self.carla_sim.get_world() + self.carla_sim.initialize_vehicle() + =} - reaction(timer_) -> velocity, position {= - self.carla_sim.tick() - print("setting velocity to ", self.carla_sim.get_vehicle_velocity()) - velocity.set(self.carla_sim.get_vehicle_velocity()) - p = self.carla_sim.get_vehicle_position() - coordinate = make_coordinate([p.latitude, p.longitude, p.altitude]) - position.set(coordinate) - =} + reaction(timer_) -> velocity, position {= + self.carla_sim.tick() + print("setting velocity to ", self.carla_sim.get_vehicle_velocity()) + velocity.set(self.carla_sim.get_vehicle_velocity()) + p = self.carla_sim.get_vehicle_position() + coordinate = make_coordinate([p.latitude, p.longitude, p.altitude]) + position.set(coordinate) + =} - reaction(command) {= - cmd = command.value - self.carla_sim.apply_control(cmd.throttle, cmd.brake) - =} + reaction(command) {= + cmd = command.value + self.carla_sim.apply_control(cmd.throttle, cmd.brake) + =} } diff --git a/experiments/Python/src/Intersection/Carla/RSU.lf b/experiments/Python/src/Intersection/Carla/RSU.lf index 0c5b131f..80031c81 100644 --- a/experiments/Python/src/Intersection/Carla/RSU.lf +++ b/experiments/Python/src/Intersection/Carla/RSU.lf @@ -1,66 +1,60 @@ /** - * This Lingua Franca program simulates a Road Side Unit (RSU), a component - * critical in Intelligent Transportation Systems for managing vehicle interactions - * at an intersection. Upon startup, the RSU is initialized with specific - * parameters such as intersection width, nominal speed in the intersection, and - * the intersection position. The RSU then listens for incoming requests from - * vehicles. These requests, which include information about the vehicle's current - * state and its intentions, are processed, and the RSU determines if the vehicle - * is granted access to the intersection. If access is granted, a grant message is - * sent back to the vehicle with key information such as the intersection position, - * target speed, and arrival time. The RSU can handle multiple vehicle requests and - * issue corresponding grants simultaneously. + * This Lingua Franca program simulates a Road Side Unit (RSU), a component critical in Intelligent + * Transportation Systems for managing vehicle interactions at an intersection. Upon startup, the + * RSU is initialized with specific parameters such as intersection width, nominal speed in the + * intersection, and the intersection position. The RSU then listens for incoming requests from + * vehicles. These requests, which include information about the vehicle's current state and its + * intentions, are processed, and the RSU determines if the vehicle is granted access to the + * intersection. If access is granted, a grant message is sent back to the vehicle with key + * information such as the intersection position, target speed, and arrival time. The RSU can handle + * multiple vehicle requests and issue corresponding grants simultaneously. */ target Python { - files: [ - "ROS/carla_intersection/src/rsu.py", - "ROS/carla_intersection/src/utils.py" - ] -}; + files: ["ROS/carla_intersection/src/rsu.py", "ROS/carla_intersection/src/utils.py"] +} preamble {= - from rsu import RSU - from utils import GenericClock, dotdict, make_coordinate + from rsu import RSU + from utils import GenericClock, dotdict, make_coordinate =} reactor RSU( - num_entries(0), - intersection_width(0), - nominal_speed_in_intersection(0.0), - intersection_position(0, 0, 0) -) { - input[num_entries] request; - output[num_entries] grant; + num_entries=0, + intersection_width=0, + nominal_speed_in_intersection=0.0, + intersection_position(0, 0, 0)) { + input[num_entries] request + output[num_entries] grant - preamble {= - class Logger: - def info(self, *args): - print(f"{lf.time.logical()} - rsu: ", args) + preamble {= + class Logger: + def info(self, *args): + print(f"{lf.time.logical()} - rsu: ", args) - class LFClock(GenericClock): - def get_current_time_in_ns(self): - return lf.time.logical() - =} + class LFClock(GenericClock): + def get_current_time_in_ns(self): + return lf.time.logical() + =} - reaction(startup) {= - self.rsu = RSU(intersection_width = self.intersection_width, - nominal_speed_in_intersection = self.nominal_speed_in_intersection, - intersection_position = make_coordinate(self.intersection_position), - clock = self.LFClock(), - logger = self.Logger()) - =} + reaction(startup) {= + self.rsu = RSU(intersection_width = self.intersection_width, + nominal_speed_in_intersection = self.nominal_speed_in_intersection, + intersection_position = make_coordinate(self.intersection_position), + clock = self.LFClock(), + logger = self.Logger()) + =} - reaction(request) -> grant {= - for i in range(self.num_entries): - if not request[i].is_present: - continue - request_ = request[i].value - pub_packets = self.rsu.receive_request(request_) - if pub_packets.grant != None: - grant_ = dotdict() - grant_.intersection_position = pub_packets.grant.intersection_position - grant_.target_speed = pub_packets.grant.target_speed - grant_.arrival_time = pub_packets.grant.arrival_time - grant[i].set(grant_) - =} + reaction(request) -> grant {= + for i in range(self.num_entries): + if not request[i].is_present: + continue + request_ = request[i].value + pub_packets = self.rsu.receive_request(request_) + if pub_packets.grant != None: + grant_ = dotdict() + grant_.intersection_position = pub_packets.grant.intersection_position + grant_.target_speed = pub_packets.grant.target_speed + grant_.arrival_time = pub_packets.grant.arrival_time + grant[i].set(grant_) + =} } diff --git a/experiments/Python/src/Intersection/Carla/Vehicle.lf b/experiments/Python/src/Intersection/Carla/Vehicle.lf index 0e0b9099..b08b6d9f 100644 --- a/experiments/Python/src/Intersection/Carla/Vehicle.lf +++ b/experiments/Python/src/Intersection/Carla/Vehicle.lf @@ -1,83 +1,74 @@ /** - * This Lingua Franca program simulates the behavior of a vehicle in a traffic - * management system, specifically in an environment with a Road Side Unit (RSU) - * managing an intersection. Upon startup, the vehicle is initialized with a unique - * identifier, its initial position, and velocity. The vehicle continually receives - * updated position and velocity information, processes these updates, and - * accordingly decides the control actions (throttle and brake) required. If the - * vehicle needs to cross the intersection, it generates a request to the RSU for - * permission. Upon receiving a grant from the RSU, the vehicle adjusts its speed - * and arrival time at the intersection based on the details provided in the grant. - * The main goal of the vehicle is to navigate the intersection safely while - * abiding by the instructions given by the RSU. + * This Lingua Franca program simulates the behavior of a vehicle in a traffic management system, + * specifically in an environment with a Road Side Unit (RSU) managing an intersection. Upon + * startup, the vehicle is initialized with a unique identifier, its initial position, and velocity. + * The vehicle continually receives updated position and velocity information, processes these + * updates, and accordingly decides the control actions (throttle and brake) required. If the + * vehicle needs to cross the intersection, it generates a request to the RSU for permission. Upon + * receiving a grant from the RSU, the vehicle adjusts its speed and arrival time at the + * intersection based on the details provided in the grant. The main goal of the vehicle is to + * navigate the intersection safely while abiding by the instructions given by the RSU. */ target Python { - files: [ - "ROS/carla_intersection/src/vehicle.py", - "ROS/carla_intersection/src/utils.py" - ] -}; + files: ["ROS/carla_intersection/src/vehicle.py", "ROS/carla_intersection/src/utils.py"] +} preamble {= - from vehicle import Vehicle - from utils import GenericClock, dotdict, make_coordinate + from vehicle import Vehicle + from utils import GenericClock, dotdict, make_coordinate =} -reactor Vehicle( - bank_index(0), - initial_velocities({=None=}), - initial_positions({=None=}) -) { - input velocity; - input position; - input grant; - output control; - output request; - state vehicle; +reactor Vehicle(bank_index=0, initial_velocities = {= None =}, initial_positions = {= None =}) { + input velocity + input position + input grant + output control + output request + state vehicle - preamble {= - class Logger: - def __init__(self, vehicle_id): - self.vehicle_id = vehicle_id + preamble {= + class Logger: + def __init__(self, vehicle_id): + self.vehicle_id = vehicle_id - def info(self, *args): - print(f"{lf.time.logical()} - vehicle_{self.vehicle_id}: ", args) + def info(self, *args): + print(f"{lf.time.logical()} - vehicle_{self.vehicle_id}: ", args) - class LFClock(GenericClock): - def get_current_time_in_ns(self): - return lf.time.logical() - =} + class LFClock(GenericClock): + def get_current_time_in_ns(self): + return lf.time.logical() + =} - reaction(startup) {= - self.vehicle = Vehicle(vehicle_id = self.bank_index, - initial_position = make_coordinate(self.initial_positions[self.bank_index]), - initial_velocity = make_coordinate(self.initial_velocities[self.bank_index]), - clock = self.LFClock(), - logger = self.Logger(self.bank_index)) - =} + reaction(startup) {= + self.vehicle = Vehicle(vehicle_id = self.bank_index, + initial_position = make_coordinate(self.initial_positions[self.bank_index]), + initial_velocity = make_coordinate(self.initial_velocities[self.bank_index]), + clock = self.LFClock(), + logger = self.Logger(self.bank_index)) + =} - reaction(position) {= - self.vehicle.set_position(position.value) - =} + reaction(position) {= + self.vehicle.set_position(position.value) + =} - reaction(velocity) -> control, request {= - new_velocity = velocity.value - pub_packets = self.vehicle.receive_velocity_from_simulator(new_velocity) - if pub_packets.cmd != None: - cmd = dotdict() - cmd.throttle = pub_packets.cmd.throttle - cmd.brake = pub_packets.cmd.brake - control.set(cmd) - if pub_packets.request != None: - request_ = dotdict() - request_.requestor_id = self.bank_index - request_.speed = pub_packets.request.speed - request_.position = pub_packets.request.position - request.set(request_) - =} + reaction(velocity) -> control, request {= + new_velocity = velocity.value + pub_packets = self.vehicle.receive_velocity_from_simulator(new_velocity) + if pub_packets.cmd != None: + cmd = dotdict() + cmd.throttle = pub_packets.cmd.throttle + cmd.brake = pub_packets.cmd.brake + control.set(cmd) + if pub_packets.request != None: + request_ = dotdict() + request_.requestor_id = self.bank_index + request_.speed = pub_packets.request.speed + request_.position = pub_packets.request.position + request.set(request_) + =} - reaction(grant) {= - grant = grant.value - self.vehicle.grant(grant.arrival_time, grant.intersection_position) - =} + reaction(grant) {= + grant = grant.value + self.vehicle.grant(grant.arrival_time, grant.intersection_position) + =} } diff --git a/experiments/Python/src/Intersection/Intersection.lf b/experiments/Python/src/Intersection/Intersection.lf index 33ef37b5..854c5a87 100644 --- a/experiments/Python/src/Intersection/Intersection.lf +++ b/experiments/Python/src/Intersection/Intersection.lf @@ -1,29 +1,25 @@ /** - * Model of a smart intersection with a road-side unit (RSU) - * that regulates the flow of automated vehicles through the - * intersection. Vehicles that are approaching the intersection - * send an initial message to the RSU with their speed and - * distance to the intersection. The RSU responds with a - * reservation for when the vehicle can enter the intersection - * and what its average speed through the intersection should be. - * - * This is meant as a supervisory controller, and it assumes that - * the vehicle is equipped with a low-level controller (or a human) - * that is responsible for lane keeping, collision avoidance, etc. - * + * Model of a smart intersection with a road-side unit (RSU) that regulates the flow of automated + * vehicles through the intersection. Vehicles that are approaching the intersection send an initial + * message to the RSU with their speed and distance to the intersection. The RSU responds with a + * reservation for when the vehicle can enter the intersection and what its average speed through + * the intersection should be. + * + * This is meant as a supervisory controller, and it assumes that the vehicle is equipped with a + * low-level controller (or a human) that is responsible for lane keeping, collision avoidance, etc. + * * This is a very rough starting point that needs a lot of work. - * - * Note: - * The 'Vehicle' controller reactor relies on an external vehicle interface - * that provides a 'vehicle_status' and a 'vehicle_position' (see the - * preamble below). To make this example interactive, a toy Simulator - * reactor is provided that outputs an initial velocity and position - * for each vehicle, and updates these values continuously using an - * input vehicle command (throttle and brake) from the vehicle controller. + * + * Note: The 'Vehicle' controller reactor relies on an external vehicle interface that provides a + * 'vehicle_status' and a 'vehicle_position' (see the preamble below). To make this example + * interactive, a toy Simulator reactor is provided that outputs an initial velocity and position + * for each vehicle, and updates these values continuously using an input vehicle command (throttle + * and brake) from the vehicle controller. */ -target Python; +target Python preamble {= + from math import sin, cos, sqrt, atan2, radians, pi import time import random @@ -140,51 +136,47 @@ goal_reached_threshold = 14.0 goal_reached_threshold_time = (goal_reached_threshold/speed_limit) =} -reactor Vehicle (vehicle_id(0)){ - input vehicle_stat; - input vehicle_pos; +reactor Vehicle(vehicle_id=0) { + input vehicle_stat + input vehicle_pos + input grant + + output request + output control + output goal_reached + + logical action delay + + state current_pos = {= coordinate(0.0, 0.0, 0.0) =} + state last_pos + state granted_time_to_enter = 0 + state intersection_pos + state goal_reached = false + state velocity = 0.0 - input grant; - - output request; - - output control; - - output goal_reached; - - logical action delay; - - state current_pos({=coordinate(0.0, 0.0, 0.0)=}); - - state last_pos; - - state granted_time_to_enter(0); - state intersection_pos; - state goal_reached(false); - state velocity(0.0); - reaction(vehicle_pos) {= - self.current_pos = vehicle_pos.value.current_pos; + self.current_pos = vehicle_pos.value.current_pos =} + reaction(vehicle_stat) -> request, control, goal_reached {= if self.goal_reached: # Nothing to do here return - + # Record the speed velocity_3d = vehicle_stat.value.velocity linear_speed = sqrt(velocity_3d.x**2 + velocity_3d.y**2 + velocity_3d.z**2) self.velocity = linear_speed - + if self.velocity == 0: # Prevent divisions by zero self.velocity = 0.001 - + # Check if we have received an initial pos if self.current_pos.distance(coordinate(0.0, 0.0, 0.0)) <= 0.00000001: print("Warning: Have not received initial pos yet.") return - + # Send a new request to the RSU if no time to enter # the intersection is granted if self.granted_time_to_enter == 0: @@ -198,11 +190,11 @@ reactor Vehicle (vehicle_id(0)){ # All we need to do is adjust our velocity # to enter the intersection at the allocated # time - + # First, how far are we from the intersection distance_remaining = self.intersection_pos.distance(self.current_pos) time_remaining = (self.granted_time_to_enter - lf.time.logical()) / (BILLION * 1.0) - + print("########################################") print("Vehicle {}: Distance to intersection: {}m.".format(self.vehicle_id + 1, distance_remaining)) print("Vehicle {}: Time to intersection: {}s.".format(self.vehicle_id + 1, time_remaining)) @@ -210,7 +202,7 @@ reactor Vehicle (vehicle_id(0)){ target_speed = 0.0 # target_speed = distance_remaining/time_remaining - + if distance_remaining <= goal_reached_threshold and \ time_remaining <= goal_reached_threshold_time : # Goal reached @@ -222,7 +214,7 @@ reactor Vehicle (vehicle_id(0)){ target_speed = speed_limit # Simulation is over self.goal_reached = True - + print("\n\n*************************************************************\n\n".format(self.vehicle_id + 1)) print("************* Vehicle {}: Reached intersection! *************".format(self.vehicle_id + 1)) print("\n\n*************************************************************\n\n".format(self.vehicle_id + 1)) @@ -239,23 +231,23 @@ reactor Vehicle (vehicle_id(0)){ # Has not reached the goal # target_speed = ((2 * distance_remaining) / (time_remaining)) - self.velocity target_speed = distance_remaining / time_remaining - + print("Vehicle {}: Calculated target speed: {}m/s.".format(self.vehicle_id + 1, target_speed)) - + if (target_speed - speed_limit) > 0: print("Warning: target speed exceeds the speed limit") target_speed = 0 self.granted_time_to_enter = 0 - + if target_speed <= 0: print("Warning: target speed negative or zero") target_speed = 0.001 self.granted_time_to_enter = 0 - + brake = 0.0 throttle = 0.0 - - if target_speed >= self.velocity: + + if target_speed >= self.velocity: # Calculate a proportional throttle (0.0 < throttle < 1.0) throttle = min((target_speed - self.velocity)/target_speed, 1) # throttle = 1.0 @@ -266,57 +258,58 @@ reactor Vehicle (vehicle_id(0)){ brake = min((self.velocity - target_speed)/self.velocity, 1) # brake = 1.0 throttle = 0.0 - + # Check throttle boundaries if throttle < 0: print("Error: negative throttle") throttle = 0 - + # Prepare and send the target velocity as a vehicle command cmd = vehicle_command(throttle = throttle, brake = brake) control.set(cmd) - + print("Vehicle {}: Throttle: {}. Brake: {}".format(self.vehicle_id + 1, throttle, brake)) =} - - reaction(grant) {= + + reaction(grant) {= print("Vehicle {} Granted access".format(self.vehicle_id + 1), - "to enter the intersection at elapsed logical time {:d}.\n".format( - int(grant.value.arrival_time) - lf.time.start() - ), - "Current elapsed logical time: {:d}, Current physical time is {:d}.".format( - lf.time.logical_elapsed(), - lf.time.physical_elapsed()) - ) - - self.granted_time_to_enter = grant.value.arrival_time - self.intersection_pos = grant.value.intersection_pos - self.goal_reached = False - =} deadline (5 sec) {= + "to enter the intersection at elapsed logical time {:d}.\n".format( + int(grant.value.arrival_time) - lf.time.start() + ), + "Current elapsed logical time: {:d}, Current physical time is {:d}.".format( + lf.time.logical_elapsed(), + lf.time.physical_elapsed()) + ) + + self.granted_time_to_enter = grant.value.arrival_time + self.intersection_pos = grant.value.intersection_pos + self.goal_reached = False + =} deadline(5 sec) {= # Ignore the grant. Will ask for another one print("Received the grant late.") self.granted_time_to_enter = 0 =} } -reactor RSU ( - num_entries(4), - intersection_width(42.0), // in meters. - // If the vehicle is told to slow down, then its target - // average speed in the intersection should be at least this. - nominal_speed_in_intersection(2.8), // In m/sec 0.6 sec to traverse. - intersection_pos({=coordinate(0.0, 0.0, 0.0)=}) // GPS coordinates for the intersection -) { - input[num_entries] request; - input[num_entries] vehicle_reached_intersection; - output[num_entries] grant; - - state earliest_free(0 msec); - state active_participants({=[0] * 20=}); - +reactor RSU( + num_entries=4, + intersection_width=42.0, # in meters. + # If the vehicle is told to slow down, then its target + # average speed in the intersection should be at least this. + # In m/sec 0.6 sec to traverse. + nominal_speed_in_intersection=2.8, + # GPS coordinates for the intersection + intersection_pos = {= coordinate(0.0, 0.0, 0.0) =}) { + input[num_entries] request + input[num_entries] vehicle_reached_intersection + output[num_entries] grant + + state earliest_free = 0 msec + state active_participants = {= [0] * 20 =} + reaction(request) -> grant {= for i in range(self.num_entries): - if request[i].is_present: + if request[i].is_present: self.active_participants[i] = 1 if request[i].value.speed == 0: # Avoid division by zero @@ -329,12 +322,12 @@ reactor RSU ( dr = self.intersection_pos.distance(request[i].value.current_pos) print("*** RSU: Vehicle {}'s distance to intersection is {}.".format(i+1, dr)) arrival_in = dr / speed_in_m_per_sec - + time_message_sent = lf.time.logical() - + # Convert the time interval to nsec (it is in seconds). arrival_time_ns = time_message_sent + (arrival_in * BILLION) - + response = vehicle_grant() if arrival_time_ns >= self.earliest_free: # Vehicle can maintain speed. @@ -345,14 +338,14 @@ reactor RSU ( response.target_speed = self.nominal_speed_in_intersection # Vehicle has to slow down and maybe stop. response.arrival_time = self.earliest_free - + response.intersection_pos = self.intersection_pos grant[i].set(response) # Update earliest free on the assumption that the vehicle # maintains its target speed (on average) within the intersection. time_in_intersection = (BILLION * self.intersection_width) / (response.target_speed) self.earliest_free = response.arrival_time + time_in_intersection - + print("*** RSU: Granted access to vehicle {} to enter at " "time {} with average target velocity {} m/s. Next available time is {}".format( i + 1, @@ -361,7 +354,7 @@ reactor RSU ( self.earliest_free - lf.time.start()) ) =} - + reaction(vehicle_reached_intersection) {= sum_of_active_participants = 0 for i in range(len(vehicle_reached_intersection)): @@ -370,87 +363,85 @@ reactor RSU ( sum_of_active_participants += self.active_participants[i] if sum_of_active_participants == 0: # End the simulation if all vehicles have reached the intersection - request_stop() + request_stop() print("\n********* SUCCESS: All vehicles have reached the intersection. *********\n") =} } -main reactor ( - num_vehicles(4), - positions({= [ # Direction (velocity vector) - coordinate(0.000038,-0.000674,2.794825), # /|\ - coordinate(-0.000501,-0.001084,2.794891), # -> - coordinate(-0.000060,-0.001510,2.794854), # \|/ - coordinate(0.000367,-0.001185,2.794846), # <- - ]=}), - initial_speeds({= \ - [ \ - vehicle_velocity(y = -8.0), vehicle_velocity(x = 8.0), \ - vehicle_velocity(y = 8.0), vehicle_velocity(x = -8.0) \ - ] - =}) -) { - vehicles = new[num_vehicles] Vehicle(); - +main reactor( + num_vehicles=4, + # Direction (velocity vector) + positions = {= + [ # Direction (velocity vector) + coordinate(0.000038,-0.000674,2.794825), # /|\ + coordinate(-0.000501,-0.001084,2.794891), # -> + coordinate(-0.000060,-0.001510,2.794854), # \|/ + coordinate(0.000367,-0.001185,2.794846), # <- + ] + =}, + initial_speeds = {= + \ + [ \ + vehicle_velocity(y = -8.0), vehicle_velocity(x = 8.0), \ + vehicle_velocity(y = 8.0), vehicle_velocity(x = -8.0) \ + ] + =}) { + vehicles = new[num_vehicles] Vehicle() + rsu = new RSU( - num_entries = num_vehicles, - intersection_pos = {=coordinate(-0.000007632,-0.001124366,2.792485)=}, - intersection_width = 28, - nominal_speed_in_intersection = 14 - ); - vehicles.request -> rsu.request; - // Simulation will end once all vehicles have reached the intersection - vehicles.goal_reached -> rsu.vehicle_reached_intersection; - rsu.grant -> vehicles.grant; - - // Handle simulation + num_entries=num_vehicles, + intersection_pos = {= coordinate(-0.000007632,-0.001124366,2.792485) =}, + intersection_width=28, + nominal_speed_in_intersection=14) + vehicles.request -> rsu.request + # Simulation will end once all vehicles have reached the intersection + vehicles.goal_reached -> rsu.vehicle_reached_intersection + rsu.grant -> vehicles.grant + + # Handle simulation simulators = new[num_vehicles] Simulator( - initial_speeds = initial_speeds, - initial_speed = {=lambda self: self.initial_speeds[self.bank_index]=}, - positions = positions, - start_pos={=lambda self: self.positions[self.bank_index]=} - ) - - simulators.vehicle_stat -> vehicles.vehicle_stat; - simulators.vehicle_pos -> vehicles.vehicle_pos; - vehicles.control -> simulators.vehicle_command; + initial_speeds=initial_speeds, + initial_speed = {= lambda self: self.initial_speeds[self.bank_index] =}, + positions=positions, + start_pos = {= lambda self: self.positions[self.bank_index] =}) + + simulators.vehicle_stat -> vehicles.vehicle_stat + simulators.vehicle_pos -> vehicles.vehicle_pos + vehicles.control -> simulators.vehicle_command } /** - * A simulator that interacts with the vehicle controllers. - * This enables this example to be self-contained, without a need - * for a simulator such as Carla. + * A simulator that interacts with the vehicle controllers. This enables this example to be + * self-contained, without a need for a simulator such as Carla. */ reactor Simulator( - interval(1 sec), // Note: For demonstration purposes, - // this interval is set to a very low frequency. - // Set the interval to 16 msecs for a more - // realistic simulation. - initial_speeds({=[]=}), - initial_speed({=vehicle_velocity(x = 12.0)=}), - positions({=[]=}), - start_pos({=coordinate(0.000042,-0.000701,2.794825)=}), - max_acceleration(4), // m/s/s - drag_acceleration({=-0.2=}) -){ - - input vehicle_command; - output vehicle_stat; - output vehicle_pos; + interval = 1 sec, # Note: For demonstration purposes, + # this interval is set to a very low frequency. + # Set the interval to 16 msecs for a more + # realistic simulation. + initial_speeds = {= [] =}, + initial_speed = {= vehicle_velocity(x = 12.0) =}, + positions = {= [] =}, + start_pos = {= coordinate(0.000042,-0.000701,2.794825) =}, + max_acceleration=4, # m/s/s + drag_acceleration = {= -0.2 =}) { + + input vehicle_command - state current_pos; - state current_velocity; - state current_throttle(0); - state current_brake(0); - state velocity_vector( - {={"x":0, "y":0}=} - ); + output vehicle_stat + output vehicle_pos + + state current_pos + state current_velocity + state current_throttle = 0 + state current_brake = 0 + state velocity_vector = {= {"x":0, "y":0} =} reaction(startup) -> vehicle_stat, vehicle_pos {= # Give initial values to the vehicle controller self.current_pos = self.start_pos(self) self.current_velocity = self.initial_speed(self) - + # For this simulation, we would like the vehicles to # move in a straight line. Therefore, we calculate a # velocity vector here and will keep it throughout the @@ -458,10 +449,9 @@ reactor Simulator( # to be able to change directions at the intersection. if not isclose(self.current_velocity.x, 0): self.velocity_vector["x"] = self.current_velocity.x / abs(self.current_velocity.x) # Keep the sign - if not isclose(self.current_velocity.y, 0): self.velocity_vector["y"] = self.current_velocity.y / abs(self.current_velocity.y) # Keep the sign - + """ print("Simulator: Vehicle {}:\n".format(self.bank_index + 1), "Initial position: x={:f}, y={:f}, z={:f}\n".format( @@ -480,7 +470,7 @@ reactor Simulator( ) ) """ - + vehicle_pos.set(vehicle_position(self.current_pos)) vehicle_stat.set(vehicle_status(self.current_velocity)) =} @@ -488,14 +478,14 @@ reactor Simulator( timer tick(0, interval) reaction(tick) -> vehicle_stat, vehicle_pos {= previous_velocity = self.current_velocity - + # Linearly calculate an acceleration based on the value of throttle and brake current_acceleration = self.current_throttle * self.max_acceleration current_acceleration -= self.current_brake * self.max_acceleration - + # Apply a constant drag current_acceleration -= self.drag_acceleration - + print("Simulator: Vehicle {}:\n".format(self.bank_index + 1), "Current position: x={:f}, y={:f}, z={:f}\n".format( self.current_pos.x, @@ -515,26 +505,25 @@ reactor Simulator( self.velocity_vector["y"] ) ) - + # Apply acceleration self.current_velocity.x += (current_acceleration * (self.interval / BILLION)) \ * self.velocity_vector["x"] # Keep the direction self.current_velocity.y += (current_acceleration * (self.interval / BILLION)) \ * self.velocity_vector["y"] # Keep the direction - + vehicle_stat.set(vehicle_status(self.current_velocity)) - + # Change the GPS position of the vehicle x_move_meters = previous_velocity.x * self.interval / BILLION y_move_meters = previous_velocity.y * self.interval / BILLION - + self.current_pos.x = self.current_pos.x + (180/pi)*(x_move_meters/6378137) self.current_pos.y = self.current_pos.y + (180/pi)*(y_move_meters/6378137) - + vehicle_pos.set(vehicle_position(self.current_pos)) =} - reaction(vehicle_command) {= # Update throttle and brake values self.current_throttle = vehicle_command.value.throttle diff --git a/experiments/Python/src/Mining/BusyMine.lf b/experiments/Python/src/Mining/BusyMine.lf index c64e100f..4cf76148 100644 --- a/experiments/Python/src/Mining/BusyMine.lf +++ b/experiments/Python/src/Mining/BusyMine.lf @@ -1,52 +1,46 @@ - /** +/** * A simple mining demo. + * * Sources: https://github.com/hbokmann/Pacman + * * LICENSE: N/A - * + * * @author Soroush Bateni * @author Benjamin Asch * * To run: - * 1- lfc src/BusyMine.lf - * 2- Follow the instructions on the terminal. - * - * General info: - * 1 - The different blocks represent different stations and - * tasks for the 'autonomous ground vehicle' to complete. - * Burgundy block- mining station - * Blue block- washing station - * Orange/yellow block- filtering station - * Gray block- storing station - * Green block- charging station - * 2 - The AGV must complete the tasks in the order specified in the reactor's - * list, while simultaneously avoiding people and battery drainage. - * 3 - A version of PhosphateMine.lf where the stations become busy. The agv - * must decide what to do in the meantime. Stations become red when busy. + * -1- lfc src/BusyMine.lf + * -2- Follow the instructions on the terminal. * + * General info: + * -1- The different blocks represent different stations and tasks for the 'autonomous ground + * vehicle' to complete. Burgundy block- mining station Blue block- washing station Orange/yellow + * block- filtering station Gray block- storing station Green block- charging station + * -2- The AGV must complete the tasks in the order specified in the reactor's list, while + * simultaneously avoiding people and battery drainage. + * -3- A version of PhosphateMine.lf where the stations become busy. The AGV must decide what to do + * in the meantime. Stations become red when busy. * * TODOs - * 1- Add more comments. - * 2- Make the demo logic more efficient if possible. - * 3- Debug A-star search function instead of using euclidean distance. - * 4- Add personalities for each person instead of following pre-determined - * directions. - * 5- Add modes for people (exploring, chasing, running away). - * 6- Enable federated execution if possible. - * 7- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the people see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . + * -1- Add more comments. + * -2- Make the demo logic more efficient if possible. + * -3- Debug A-star search function instead of using euclidean distance. + * -4- Add personalities for each person instead of following pre-determined directions. + * -5- Add modes for people (exploring, chasing, running away). + * -6- Enable federated execution if possible. + * -7- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the people see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. * -**/ - + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -92,7 +86,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -103,22 +97,24 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -126,22 +122,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 16) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -158,14 +152,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Processed Material: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) =} reaction(moving_sprites) {= - self._screen.fill(mine.white) agv = mine.pygame.sprite.Sprite() sprite_list = mine.pygame.sprite.RenderPlain() @@ -179,7 +172,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -203,43 +196,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Mining Complete!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - #print("game is over") - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Mining Complete!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + #print("game is over") + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -248,7 +242,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -264,15 +258,18 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -281,19 +278,27 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), energy_cost(-0.25), charge_at(30), risk(120)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + energy_cost=-0.25, + charge_at=30, + risk=120) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + # Receive updated wall list + input wall_list + # Receive updated gate list + input gate_list input icon input mines input wash @@ -304,9 +309,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ logical action schedule_filter logical action schedule_wash - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list @@ -316,13 +321,13 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ state _store state _charger state _stations - state _pause({=False=}) - state _find_moves(0) - state _go_back({=False=}) - #state _event_list({=["mining", "washing", "filtering", "storing"]=}) - state _action({="mining"=}) - state _prev_action({=["mining"]=}) - state _scheduled(True) + state _pause = {= False =} + state _find_moves = 0 + state _go_back = {= False =} + # state _event_list({=["mining", "washing", "filtering", "storing"]=}) + state _action = {= "mining" =} + state _prev_action = {= ["mining"] =} + state _scheduled = True output sprite output icon_name @@ -384,9 +389,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action = "washing" else: self._action = "mining" - =} - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -412,25 +416,26 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._store.restart() self._charger.restart() =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - reaction(pygame_event) -> sprite, playerpause, restart, reset(Close), omines, owash, ofilt, ostore, ocharger {= + reaction(pygame_event) -> + sprite, playerpause, restart, reset(Close), omines, owash, ofilt, ostore, ocharger {= #print("action is ", self._action) #print("prev action is ", self._prev_action[-1]) keyboard_events = mine.pygame.event.get() for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: if event.key == mine.pygame.K_ESCAPE: request_stop() @@ -465,8 +470,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -506,7 +511,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ if not self._pause and self.character_instance.battery > 0: #and self._active if self._ai_control: Close.set() - =} reaction(people_sprites) {= @@ -521,15 +525,19 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._pause = True self.character_instance.speedzero() =} + } - } mode Charge_check { + mode Charge_check { reaction(reset) -> reset(Location_check) {= if self.character_instance.battery <= self.charge_at: self._action = "charging" Location_check.set() =} - } mode Location_check { - reaction(reset) -> reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= + } + + mode Location_check { + reaction(reset) -> + reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= if self._action == "mining" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.minespot: mining.set() elif self._action == "washing" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.washspot: @@ -537,14 +545,15 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ elif self._action == "filtering" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.filterspot: filtering.set() elif self._action == "storing" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.storespot: - storing.set() + storing.set() elif self._action == "charging" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.chargerspot: charging.set() else: findspot.set() - =} - } mode findspot { + } + + mode findspot { reaction(reset) -> history(export) {= for station in self._stations: if station is not None and self._action == station.name: @@ -565,7 +574,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._find_moves = self.character_instance.num_moves export.set() =} - } mode mining { + } + + mode mining { reaction(reset) -> history(export) {= for i in range(1): if randint(1, 10) > 5: @@ -579,9 +590,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #print("next set action is ", self._action) export.set() =} + } - } mode washing { - + mode washing { reaction(reset) -> schedule_wash, history(export) {= #print("in washing") if len(self._wash.cargo) > 0: @@ -590,12 +601,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.filt_cargo.append(item) self.character_instance.num_tot_cargo += 1 self._wash.cargo = [] - + for item in self.character_instance.wash_cargo: print("loading items to wash") self._wash.cargo.append(mine.Cargo("filtering")) self.character_instance.num_tot_cargo -= 1 - + self.character_instance.wash_cargo = [] self._wash.set_busy() self._scheduled = True @@ -604,8 +615,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self.setNextAction() export.set() =} + } - } mode charging { + mode charging { reaction(reset) -> history(export) {= #print("in charging") if self.character_instance.battery < 100: @@ -620,7 +632,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self._action, self._prev_action = self._prev_action, self._action export.set() =} - } mode filtering { + } + + mode filtering { reaction(reset) -> schedule_filter, history(export) {= #print("in filtering") if len(self._filt.cargo) > 0: @@ -642,20 +656,23 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self.setNextAction() export.set() =} - } mode storing { + } + + mode storing { reaction(reset) -> history(export) {= - #print("in storing") + #print("in storing") self._scheduled = True self._store.set_busy() self._prev_action.append("storing") #self.setNextAction() self.character_instance.store_processed() self.character_instance.store_cargo = [] - + export.set() =} - } mode Close { + } + mode Close { reaction(reset) -> reset(Charge_check), reset(Avoid), history(export) {= #print("in mode close") if len(self._people) > 0: @@ -667,9 +684,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ else: export.set() =} + } - } mode Avoid { - + mode Avoid { reaction(reset) -> history(export) {= #print("avoid time") self.character_instance.ai_avoid(self._layout, self._people, 7) @@ -677,7 +694,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} - } reaction(schedule_filter) {= @@ -691,19 +707,19 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -731,7 +747,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -745,7 +761,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -753,34 +769,34 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart input in_mines input in_charger input in_wash input in_filt input in_store - - state _wall_list + + state _wall_list state _stations_list - state _gate + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(100) - state _score(0) + state _score_to_win = 100 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -816,7 +832,7 @@ reactor GameController(number_of_people(4)) { # filt.set(self._filter) # store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -826,7 +842,7 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= if self._score == self._score_to_win: game_over.set(True) - + score.set(agv_sprite.value.total_stored) =} @@ -836,12 +852,11 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} reaction(in_mines, in_wash, in_filt, in_store, in_charger) -> mines, charger, wash, filt, store {= @@ -875,18 +890,17 @@ reactor GameController(number_of_people(4)) { self._charger = station charger.set(station) =} - + reaction(restart) {= if restart.value: - + self._score_to_win = 100 self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -894,73 +908,67 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 10, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=10, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list agv.omines, agv.owash, agv.ofilt, agv.ostore, agv.ocharger -> - controller.in_mines, controller.in_wash, controller.in_filt, - controller.in_store, controller.in_charger - // controller.mines, controller.wash, controller.filt, controller.store, - // controller.charger -> agv.mines, agv.wash, agv.filt, agv.store, agv.charger - - # Send the sprites to the display to be drawn - #controller.block_list - agv.sprite, - peoples.sprite, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.moving_sprites - - controller.wall_list, controller.gate -> - display.static_sprites - - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + controller.in_mines, + controller.in_wash, + controller.in_filt, + controller.in_store, + controller.in_charger + + # controller.mines, controller.wash, controller.filt, controller.store, + # controller.charger -> agv.mines, agv.wash, agv.filt, agv.store, agv.charger + # Send the sprites to the display to be drawn + # controller.block_list + agv.sprite, + peoples.sprite, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.moving_sprites + + controller.wall_list, controller.gate -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites, agv.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - - #controller.block_list -> agv.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Mining/MineTest.lf b/experiments/Python/src/Mining/MineTest.lf index 3e053f98..d51e408a 100644 --- a/experiments/Python/src/Mining/MineTest.lf +++ b/experiments/Python/src/Mining/MineTest.lf @@ -1,36 +1,38 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/mine.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/mine.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -76,7 +78,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +86,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +117,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 18) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -144,7 +146,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) @@ -163,7 +165,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -176,7 +178,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("Paused. Press SPACE to continue,", True, mine.black) self._screen.blit(text2, [135, 303]) text3=self._font.render("Press M to toggle AI.", True, mine.black) @@ -184,43 +186,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Area Cleared!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Area Cleared!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -229,7 +232,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -245,15 +248,18 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -262,47 +268,51 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), energy_cost(-0.5)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + energy_cost=-0.5) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _eat_moves(0) - state _action({="mining"=}) + state _pause = {= False =} + state _eat_moves = 0 + state _action = {= "mining" =} output sprite output icon_name output playerpause output restart - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value @@ -316,7 +326,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -345,8 +355,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -369,7 +379,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ if not self._pause and self.character_instance.battery > 0: #and self._active if self._ai_control: Close.set() - =} reaction(people_sprites) {= @@ -384,14 +393,17 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._pause = True self.character_instance.speedzero() =} + } - } mode Charge_check { + mode Charge_check { reaction(reset) -> reset(Close) {= if self.character_instance.battery <= self.charge_at: self._action = "charging" Close.set() =} - } mode Location_check { + } + + mode Location_check { reaction(reset) -> reset(findspot) {= if self._action == "mining" and [self.character_instance.rect.left, self.character_instance.rect.top] == mine.minespot: mining.set() @@ -400,21 +412,24 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ elif self._action == "filtering" and [self.character_instance.rect.left, self.character_instance.rect.top] == mine.filterspot: filtering.set() elif self._action == "storing" and [self.character_instance.rect.left, self.character_instance.rect.top] == mine.storespot: - storing.set() + storing.set() elif self._action == "storing" and [self.character_instance.rect.left, self.character_instance.rect.top] == mine.storespot: charging.set() else: findspot.set() =} - } mode findspot { + } + + mode findspot { reaction(startup) -> reset(Location_check) {= # implement to go to intended location cond = False if cond: Location_check.set() =} - } mode Close { + } + mode Close { reaction(reset) -> reset(Avoid), history(export) {= #print("in mode close") if len(self._people) > 0: @@ -427,9 +442,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ else: export.set() =} + } - } mode Avoid { - + mode Avoid { reaction(reset) -> history(export) {= #print("avoid time") self.character_instance.ai_avoid(self._layout, self._people, 7) @@ -438,25 +453,24 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} - } } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -484,7 +498,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -498,7 +512,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -506,28 +520,28 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate + + state _wall_list + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(0) - state _score(0) + state _score_to_win = 0 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -545,7 +559,7 @@ reactor GameController(number_of_people(4)) { filt.set(self._filter) store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -554,14 +568,14 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= # blocks_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, self._block_list, True) - + # Check the list of collisions. #if len(blocks_hit_list) > 0: # self._score +=len(blocks_hit_list) #if self._score == self._score_to_win: # game_over.set(True) - + score.set(self._score) =} @@ -571,27 +585,24 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} - reaction(restart) {= if restart.value: - + self._score_to_win = 5 #print(self._energizer_list) self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -599,63 +610,58 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 5, num_static_sprites = 7 ) + display = new Display(num_moving_sprites=5, num_static_sprites=7) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - # Send the sprites to the display to be drawn - #controller.block_list - agv.sprite, - peoples.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - - controller.wall_list, controller.gate, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.static_sprites - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + # Send the sprites to the display to be drawn + # controller.block_list + agv.sprite, peoples.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list + + controller.wall_list, + controller.gate, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites, agv.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - #controller.block_list -> agv.block_list - } diff --git a/experiments/Python/src/Mining/MineWObserver.lf b/experiments/Python/src/Mining/MineWObserver.lf index 567a9a12..66440819 100644 --- a/experiments/Python/src/Mining/MineWObserver.lf +++ b/experiments/Python/src/Mining/MineWObserver.lf @@ -1,36 +1,38 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/mine.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/mine.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -76,7 +78,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +86,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +117,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 18) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -145,14 +147,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Mined Material: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) =} reaction(moving_sprites) {= - #print("adding moving sprites") self._screen.fill(mine.white) agv = mine.pygame.sprite.Sprite() @@ -166,7 +167,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -187,43 +188,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Mining Complete!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - print("game is over") - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Mining Complete!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + print("game is over") + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -232,7 +234,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -248,12 +250,11 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } -reactor Observer(risk(160)) { +reactor Observer(risk=160) { input[5] moving_sprites - state _layout({=mine.walls=}) + state _layout = {= mine.walls =} output warning reaction(moving_sprites) -> warning {= @@ -265,22 +266,25 @@ reactor Observer(risk(160)) { agv = sprite.value else: people.append(sprite.value) - + if ai.euclid_close_people(people, agv.rect.left, agv.rect.top)[1] <= self.risk: path = ai.peopleavoid(self._layout, people, agv.rect.left, agv.rect.top) warning.set([path[0][0], path[0][1]]) else: warning.set([]) =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -289,37 +293,42 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), energy_cost(-0.25), charge_at(30), risk(120)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + energy_cost=-0.25, + charge_at=30, + risk=120) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon input warning logical action scheduler - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _find_moves(0) - state _event_list({=["mining", "washing", "filtering", "storing"]=}) - state _action({=self._event_list[0]=}) - state _prev_action({=self._event_list[0]=}) - state _scheduled(True) - state _warned({=[]=}) - + state _pause = {= False =} + state _find_moves = 0 + state _event_list = {= ["mining", "washing", "filtering", "storing"] =} + state _action = {= self._event_list[0] =} + state _prev_action = {= self._event_list[0] =} + state _scheduled = True + state _warned = {= [] =} output sprite output icon_name @@ -337,18 +346,18 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._event_list = self._event_list[1:] self._event_list.append(temp) =} - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value @@ -363,7 +372,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -394,8 +403,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -422,7 +431,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ Avoid.set() else: Charge_check.set() - =} reaction(warning) {= @@ -435,16 +443,20 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._pause = True self.character_instance.speedzero() =} + } - } mode Charge_check { + mode Charge_check { reaction(reset) -> reset(Location_check) {= #print("checking charge") if self.character_instance.battery <= self.charge_at: self._action = "charging" Location_check.set() =} - } mode Location_check { - reaction(reset) -> reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= + } + + mode Location_check { + reaction(reset) -> + reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= #print("checking location") if self._action == "mining" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.minespot: mining.set() @@ -453,15 +465,16 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ elif self._action == "filtering" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.filterspot: filtering.set() elif self._action == "storing" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.storespot: - storing.set() + storing.set() elif self._action == "charging" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.chargerspot: charging.set() else: #print("setting findspot") findspot.set() - =} - } mode findspot { + } + + mode findspot { reaction(reset) -> history(export) {= #print("in findspot") if self._action == "mining": @@ -480,8 +493,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._find_moves = self.character_instance.num_moves export.set() =} - } mode mining { - #implement + } + + mode mining { + # implement + # reaction(scheduler) -> history(export) {= + #=} reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -496,13 +513,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #print("ending mining, beginning wash") export.set() =} + } - #reaction(scheduler) -> history(export) {= - + mode washing { + # reaction(scheduler) -> history(export) {= #=} - - } mode washing { - reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(3)) @@ -515,12 +530,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action = self._event_list[0] export.set() =} + } - #reaction(scheduler) -> history(export) {= - - #=} - - } mode charging { + mode charging { reaction(reset) -> history(export) {= print("in charging") if self.character_instance.battery < 100: @@ -529,7 +541,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action, self._prev_action = self._prev_action, self._action export.set() =} - } mode filtering { + } + + mode filtering { + # reaction(scheduler) -> history(export) {= + #=} reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -542,11 +558,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action = self._event_list[0] export.set() =} + } - #reaction(scheduler) -> history(export) {= - + mode storing { + # reaction(scheduler) -> history(export) {= #=} - } mode storing { reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -560,12 +576,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.store(randint(7, 11)) export.set() =} + } - #reaction(scheduler) -> history(export) {= - - #=} - } mode Avoid { - + mode Avoid { reaction(reset) -> history(export) {= print("avoid time") move = self._warned.pop() @@ -575,25 +588,24 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} - } } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -621,7 +633,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -635,7 +647,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -643,28 +655,28 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate + + state _wall_list + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(100) - state _score(0) + state _score_to_win = 100 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -682,7 +694,7 @@ reactor GameController(number_of_people(4)) { filt.set(self._filter) store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -691,14 +703,14 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= # blocks_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, self._block_list, True) - + # Check the list of collisions. #if len(blocks_hit_list) > 0: # self._score +=len(blocks_hit_list) if self._score == self._score_to_win: game_over.set(True) - + score.set(agv_sprite.value.total_stored) =} @@ -708,27 +720,24 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} - reaction(restart) {= if restart.value: - + self._score_to_win = 100 #print(self._energizer_list) self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -736,21 +745,20 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 5, num_static_sprites = 7 ) + display = new Display(num_moving_sprites=5, num_static_sprites=7) observer = new Observer() @@ -759,45 +767,40 @@ main reactor { # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - # Send the sprites to the display to be drawn - #controller.block_list - (agv.sprite, - peoples.sprite)+ -> - display.moving_sprites, observer.moving_sprites - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - - controller.wall_list, controller.gate, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.static_sprites - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + # Send the sprites to the display to be drawn + # controller.block_list + (agv.sprite, peoples.sprite)+ -> display.moving_sprites, observer.moving_sprites + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list + + controller.wall_list, + controller.gate, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - #controller.block_list -> agv.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Mining/ModalMine.lf b/experiments/Python/src/Mining/ModalMine.lf index 337bedd1..80f6a688 100644 --- a/experiments/Python/src/Mining/ModalMine.lf +++ b/experiments/Python/src/Mining/ModalMine.lf @@ -1,36 +1,36 @@ - /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman +/** + * A simple Pacman game. Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/mine.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/mine.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -76,7 +76,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +84,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +115,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 18) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -145,14 +145,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) =} reaction(moving_sprites) {= - print("adding moving sprites") self._screen.fill(mine.white) agv = mine.pygame.sprite.Sprite() @@ -166,7 +165,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -187,43 +186,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Area Cleared!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - print("game is over") - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Area Cleared!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + print("game is over") + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -232,7 +232,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -248,15 +248,18 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -265,32 +268,39 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), event_list({=["mining", "washing", "filtering", "storing"]=}), energy_cost(-0.5), charge_at(20), risk(180)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + event_list = {= ["mining", "washing", "filtering", "storing"] =}, + energy_cost=-0.5, + charge_at=20, + risk=180) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon logical action scheduler - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _find_moves(0) - state _prev_action({=["mining"]=}) + state _pause = {= False =} + state _find_moves = 0 + state _prev_action = {= ["mining"] =} output sprite output icon_name @@ -329,38 +339,36 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.payCost() =} - // method performNext() {= - // if self.event_list[0] == "mining": - // self.event_list = self.event_list[1:] - // self.event_list.append("mining") - // mining.set() - // elif self.event_list[0] == "filtering": - // self.event_list = self.event_list[1:] - // self.event_list.append("filtering") - // filtering.set() - // elif self.event_list[0] == "washing": - // self.event_list = self.event_list[1:] - // self.event_list.append("washing") - // washing.set() - // elif self.event_list[0] == "storing": - // self.event_list = self.event_list[1:] - // self.event_list.append("storing") - // storing.set() - // =} - - // method performLast() {= - // if self._prev_action[0] == "mining": - // mining.set() - // elif self._prev_action[0] == "filtering": - // filtering.set() - // elif self._prev_action[0] == "washing": - // washing.set() - // elif self._prev_action[0] == "storing": - // storing.set() - // elif self._prev_action[0] == "charging": - // charging.set() - // =} - + # method performNext() {= + # if self.event_list[0] == "mining": + # self.event_list = self.event_list[1:] + # self.event_list.append("mining") + # mining.set() + # elif self.event_list[0] == "filtering": + # self.event_list = self.event_list[1:] + # self.event_list.append("filtering") + # filtering.set() + # elif self.event_list[0] == "washing": + # self.event_list = self.event_list[1:] + # self.event_list.append("washing") + # washing.set() + # elif self.event_list[0] == "storing": + # self.event_list = self.event_list[1:] + # self.event_list.append("storing") + # storing.set() + # =} + # method performLast() {= + # if self._prev_action[0] == "mining": + # mining.set() + # elif self._prev_action[0] == "filtering": + # filtering.set() + # elif self._prev_action[0] == "washing": + # washing.set() + # elif self._prev_action[0] == "storing": + # storing.set() + # elif self._prev_action[0] == "charging": + # charging.set() + # =} method payCost() {= if self.character_instance.last_move is not [0, 0]: self.character_instance.charge(self.energy_cost) @@ -397,10 +405,10 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - initial mode mining { - reaction(pygame_event) -> sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= + initial mode mining { + reaction(pygame_event) -> + sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= print("in export mode") #print("pause is ", self._pause) #print("ai control is ", self._ai_control) @@ -408,7 +416,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -437,8 +445,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -483,13 +491,14 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ storing.set() else: self.Approach() - - =} + } - } mode washing { - - reaction(pygame_event) -> sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= + mode washing { + # reaction(scheduler) -> history(export) {= + #=} + reaction(pygame_event) -> + sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= print("in export mode") #print("pause is ", self._pause) #print("ai control is ", self._ai_control) @@ -497,7 +506,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -526,8 +535,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -572,16 +581,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ storing.set() else: self.Approach() - - =} + } - #reaction(scheduler) -> history(export) {= - - #=} - - } mode charging { - reaction(pygame_event) -> sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= + mode charging { + reaction(pygame_event) -> + sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= print("in export mode") #print("pause is ", self._pause) #print("ai control is ", self._ai_control) @@ -589,7 +594,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -618,8 +623,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -664,11 +669,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ storing.set() else: self.Approach() - - =} - } mode filtering { - reaction(pygame_event) -> sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= + } + + mode filtering { + reaction(pygame_event) -> + sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= print("in export mode") #print("pause is ", self._pause) #print("ai control is ", self._ai_control) @@ -676,7 +682,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -705,8 +711,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -751,11 +757,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ storing.set() else: self.Approach() - - =} - } mode storing { - reaction(pygame_event) -> sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= + } + + mode storing { + reaction(pygame_event) -> + sprite, playerpause, restart, reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing) {= print("in export mode") #print("pause is ", self._pause) #print("ai control is ", self._ai_control) @@ -763,7 +770,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -792,8 +799,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -838,10 +845,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ storing.set() else: self.Approach() - - =} - } + } reaction(people_sprites) {= self._ghosts = [] @@ -859,19 +864,19 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -899,7 +904,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -913,7 +918,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -921,28 +926,28 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate + + state _wall_list + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(0) - state _score(0) + state _score_to_win = 0 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -960,7 +965,7 @@ reactor GameController(number_of_people(4)) { filt.set(self._filter) store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -969,14 +974,14 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= # blocks_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, self._block_list, True) - + # Check the list of collisions. #if len(blocks_hit_list) > 0: # self._score +=len(blocks_hit_list) #if self._score == self._score_to_win: # game_over.set(True) - + score.set(self._score) =} @@ -986,27 +991,24 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} - reaction(restart) {= if restart.value: - + self._score_to_win = 5 #print(self._energizer_list) self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -1014,64 +1016,58 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 5, num_static_sprites = 7 ) + display = new Display(num_moving_sprites=5, num_static_sprites=7) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - # Send the sprites to the display to be drawn - #controller.block_list - agv.sprite, - peoples.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - - controller.wall_list, controller.gate, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.static_sprites - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + # Send the sprites to the display to be drawn + # controller.block_list + agv.sprite, peoples.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list + + controller.wall_list, + controller.gate, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites, agv.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - #controller.block_list -> agv.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Mining/PhosphateMine.lf b/experiments/Python/src/Mining/PhosphateMine.lf index 0982165e..c0e65a8c 100644 --- a/experiments/Python/src/Mining/PhosphateMine.lf +++ b/experiments/Python/src/Mining/PhosphateMine.lf @@ -1,36 +1,38 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/mine.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/mine.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -76,7 +78,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +86,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +117,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 18) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -145,14 +147,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Mined Material: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) =} reaction(moving_sprites) {= - #print("adding moving sprites") self._screen.fill(mine.white) agv = mine.pygame.sprite.Sprite() @@ -166,7 +167,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -187,43 +188,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Mining Complete!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - print("game is over") - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Mining Complete!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + print("game is over") + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -232,7 +234,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -248,15 +250,18 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -265,35 +270,41 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), energy_cost(-0.25), charge_at(30), risk(120)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + energy_cost=-0.25, + charge_at=30, + risk=120) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon logical action scheduler - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _find_moves(0) - state _event_list({=["washing", "mining", "washing", "filtering", "storing"]=}) - state _action({=self._event_list[0]=}) - state _prev_action({=self._event_list[0]=}) - state _scheduled(True) + state _pause = {= False =} + state _find_moves = 0 + state _event_list = {= ["washing", "mining", "washing", "filtering", "storing"] =} + state _action = {= self._event_list[0] =} + state _prev_action = {= self._event_list[0] =} + state _scheduled = True output sprite output icon_name @@ -311,18 +322,18 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._event_list = self._event_list[1:] self._event_list.append(temp) =} - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value @@ -337,7 +348,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -368,8 +379,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -392,7 +403,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ if not self._pause and self.character_instance.battery > 0: #and self._active if self._ai_control: Close.set() - =} reaction(people_sprites) {= @@ -407,16 +417,20 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._pause = True self.character_instance.speedzero() =} + } - } mode Charge_check { + mode Charge_check { reaction(reset) -> reset(Location_check) {= #print("checking charge") if self.character_instance.battery <= self.charge_at: self._action = "charging" Location_check.set() =} - } mode Location_check { - reaction(reset) -> reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= + } + + mode Location_check { + reaction(reset) -> + reset(charging), reset(mining), reset(washing), reset(filtering), reset(storing), reset(findspot) {= #print("checking location") if self._action == "mining" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.minespot: mining.set() @@ -425,15 +439,16 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ elif self._action == "filtering" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.filterspot: filtering.set() elif self._action == "storing" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.storespot: - storing.set() + storing.set() elif self._action == "charging" and [self.character_instance.rect.left + 16, self.character_instance.rect.top + 16] == mine.chargerspot: charging.set() else: #print("setting findspot") findspot.set() - =} - } mode findspot { + } + + mode findspot { reaction(reset) -> history(export) {= #print("in findspot") if self._action == "mining": @@ -452,8 +467,12 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._find_moves = self.character_instance.num_moves export.set() =} - } mode mining { - #implement + } + + mode mining { + # implement + # reaction(scheduler) -> history(export) {= + #=} reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -468,13 +487,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #print("ending mining, beginning wash") export.set() =} + } - #reaction(scheduler) -> history(export) {= - + mode washing { + # reaction(scheduler) -> history(export) {= #=} - - } mode washing { - reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(3)) @@ -487,12 +504,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action = self._event_list[0] export.set() =} + } - #reaction(scheduler) -> history(export) {= - - #=} - - } mode charging { + mode charging { reaction(reset) -> history(export) {= print("in charging") if self.character_instance.battery < 100: @@ -501,7 +515,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action, self._prev_action = self._prev_action, self._action export.set() =} - } mode filtering { + } + + mode filtering { + # reaction(scheduler) -> history(export) {= + #=} reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -514,11 +532,11 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._action = self._event_list[0] export.set() =} + } - #reaction(scheduler) -> history(export) {= - + mode storing { + # reaction(scheduler) -> history(export) {= #=} - } mode storing { reaction(reset) -> history(export) {= # if self._scheduled: # scheduler.schedule(SEC(4)) @@ -532,12 +550,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.store(randint(7, 11)) export.set() =} + } - #reaction(scheduler) -> history(export) {= - - #=} - } mode Close { - + mode Close { reaction(reset) -> reset(Charge_check), reset(Avoid), history(export) {= print("in mode close") if len(self._people) > 0: @@ -550,9 +565,9 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ else: export.set() =} + } - } mode Avoid { - + mode Avoid { reaction(reset) -> history(export) {= print("avoid time") self.character_instance.ai_avoid(self._layout, self._people, 7) @@ -560,25 +575,24 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} - } } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -606,7 +620,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -620,7 +634,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -628,28 +642,28 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate + + state _wall_list + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(100) - state _score(0) + state _score_to_win = 100 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -667,7 +681,7 @@ reactor GameController(number_of_people(4)) { filt.set(self._filter) store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -676,14 +690,14 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= # blocks_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, self._block_list, True) - + # Check the list of collisions. #if len(blocks_hit_list) > 0: # self._score +=len(blocks_hit_list) if self._score == self._score_to_win: game_over.set(True) - + score.set(agv_sprite.value.total_stored) =} @@ -693,27 +707,24 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} - reaction(restart) {= if restart.value: - + self._score_to_win = 100 #print(self._energizer_list) self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -721,64 +732,58 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 5, num_static_sprites = 7 ) + display = new Display(num_moving_sprites=5, num_static_sprites=7) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - # Send the sprites to the display to be drawn - #controller.block_list - agv.sprite, - peoples.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - - controller.wall_list, controller.gate, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.static_sprites - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + # Send the sprites to the display to be drawn + # controller.block_list + agv.sprite, peoples.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list + + controller.wall_list, + controller.gate, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites, agv.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - #controller.block_list -> agv.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Mining/SingleModeMine.lf b/experiments/Python/src/Mining/SingleModeMine.lf index 71c797e9..f40e57d7 100644 --- a/experiments/Python/src/Mining/SingleModeMine.lf +++ b/experiments/Python/src/Mining/SingleModeMine.lf @@ -1,36 +1,38 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/mine.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/mine.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbphosphate.py", "include/images", "include/AIPhosphate.py"] -}; +} preamble {= import os @@ -76,7 +78,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +86,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=mine.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= mine.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) agv_icon = mine.pygame.image.load(os.path.join(dirname, self.nav_icon)) mine.pygame.display.set_icon(agv_icon) - self._clock = mine.pygame.time.Clock() + self._clock = mine.pygame.time.Clock() # Create an 606x606 sized screen self._screen = mine.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +117,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = mine.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(mine.white) mine.pygame.font.init() self._font = mine.pygame.font.Font("freesansbold.ttf", 18) self._screen.fill(mine.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(mine.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= mine.pygame.display.flip() self._clock.tick() @@ -145,14 +147,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p print(self._static_sprites) self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, mine.black) self._screen.blit(self._top_corner_text, [10, 10]) =} reaction(moving_sprites) {= - print("adding moving sprites") self._screen.fill(mine.white) agv = mine.pygame.sprite.Sprite() @@ -166,7 +167,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite_list.add(sprite.value) elif isinstance(sprite.value, mine.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -187,43 +188,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p mine.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = mine.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Area Cleared!", True, mine.black) - else: - text1=self._font.render("Collision detected.", True, mine.black) - self._screen.blit(text1, [235, 233]) - print("game is over") - text2=self._font.render("To play again, press ENTER.", True, mine.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, mine.black) - self._screen.blit(text3, [165, 333]) - - mine.pygame.display.flip() + if game_over.is_present: + self._game_over = True + w = mine.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Area Cleared!", True, mine.black) + else: + text1=self._font.render("Collision detected.", True, mine.black) + self._screen.blit(text1, [235, 233]) + print("game is over") + text2=self._font.render("To play again, press ENTER.", True, mine.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, mine.black) + self._screen.blit(text3, [165, 333]) + mine.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=mine.AGV=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= mine.AGV =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -232,7 +234,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -248,15 +250,18 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=mine.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.Player =}) { input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon state character_instance @@ -265,35 +270,42 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({=mine.AGV=}), event_list({=["mining", "washing", "filtering", "storing"]=}), energy_cost(-0.5), charge_at(20), risk(180)) { +reactor AGV( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= mine.AGV =}, + event_list = {= ["mining", "washing", "filtering", "storing"] =}, + energy_cost=-0.5, + charge_at=20, + risk=180) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input[4] people_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon logical action scheduler - state _people({=[]=}) - state _layout({=mine.walls=}) - state _ai_control(True) + state _people = {= [] =} + state _layout = {= mine.walls =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _find_moves(0) - state _prev_action({=["mining"]=}) - state _action({=["mining"]=}) - state _charging(False) - state _event_list({=self.event_list=}) + state _pause = {= False =} + state _find_moves = 0 + state _prev_action = {= ["mining"] =} + state _action = {= ["mining"] =} + state _charging = False + state _event_list = {= self.event_list =} output sprite output icon_name @@ -390,7 +402,7 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ for event in keyboard_events: if event.type == mine.pygame.QUIT: request_stop() - + if event.type == mine.pygame.KEYDOWN: #print("detecting key down") if event.key == mine.pygame.K_ESCAPE: @@ -419,8 +431,8 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.character_instance.changespeed(0, -30) if event.key == mine.pygame.K_DOWN or event.key == mine.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == mine.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == mine.pygame.K_LEFT or event.key == mine.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == mine.pygame.K_RIGHT or event.key == mine.pygame.K_d: @@ -450,8 +462,6 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self.proceedNext() else: self.Approach() - - =} reaction(people_sprites) {= @@ -466,24 +476,23 @@ reactor AGV(width(0), height(0), image("images/Trollman.png"), character_class({ self._pause = True self.character_instance.speedzero() =} - } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor People(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - + + state turn = 0 + state steps = 0 + state _active = True + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -511,7 +520,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -525,7 +534,7 @@ reactor People (directions({=()=}), name("Stinky")) extends BaseCharacter { } #### Controller -reactor GameController(number_of_people(4)) { +reactor GameController(number_of_people=4) { output wall_list # List of walls on the map output gate output mines @@ -533,28 +542,28 @@ reactor GameController(number_of_people(4)) { output wash output filt output store - output score # The game score + output score # The game score output game_over input[number_of_people] people_sprites input agv_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate + + state _wall_list + state _gate state _mine state _charger state _wash state _filter state _store - state _score_to_win(0) - state _score(0) + state _score_to_win = 0 + state _score = 0 state _agv_sprite - state _agv_collide({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_list({=mine.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - + state _agv_collide = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_list = {= mine.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + reaction(startup) -> wall_list, gate, mines, charger, wash, filt, store {= _all_sprites_list = mine.pygame.sprite.RenderPlain() self._wall_list = mine.setupMineWalls(_all_sprites_list) @@ -572,7 +581,7 @@ reactor GameController(number_of_people(4)) { filt.set(self._filter) store.set(self._store) =} - + reaction(agv_sprite) {= self._agv_collide.empty() self._agv_collide.add(agv_sprite.value) @@ -581,14 +590,14 @@ reactor GameController(number_of_people(4)) { reaction(agv_sprite) -> score, game_over {= # blocks_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, self._block_list, True) - + # Check the list of collisions. #if len(blocks_hit_list) > 0: # self._score +=len(blocks_hit_list) #if self._score == self._score_to_win: # game_over.set(True) - + score.set(self._score) =} @@ -598,27 +607,24 @@ reactor GameController(number_of_people(4)) { for person in people_sprites: if person.is_present: monsta_list.add(person.value) - + monsta_hit_list = mine.pygame.sprite.spritecollide(self._agv_sprite, monsta_list, False) if monsta_hit_list: game_over.set(False) - =} - reaction(restart) {= if restart.value: - + self._score_to_win = 5 #print(self._energizer_list) self._score = 0 =} - + reaction(shutdown) {= mine.pygame.quit() =} - } main reactor { @@ -626,64 +632,58 @@ main reactor { controller = new GameController() ### Model(s) - agv = new AGV(width = {=mine.w=}, height = {=mine.p_h=}, image = "images/roomb1.png") - #width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" - + agv = new AGV(width = {= mine.w =}, height = {= mine.p_h =}, image="images/roomb1.png") + + # width = {=mine.w=}, height = {=mine.p_h=}, image = "images/mine.png" # Ghosts peoples = new[4] People( - width = {= people_specs[bank_index]["width"] =}, - height = {= people_specs[bank_index]["height"] =}, - directions = {= people_specs[bank_index]["directions"] =}, - name = {= people_specs[bank_index]["name"] =}, - character_class = ({=mine.People=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= people_specs[bank_index]["width"] =}, + height = {= people_specs[bank_index]["height"] =}, + directions = {= people_specs[bank_index]["directions"] =}, + name = {= people_specs[bank_index]["name"] =}, + character_class = {= mine.People =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 5, num_static_sprites = 7 ) + display = new Display(num_moving_sprites=5, num_static_sprites=7) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> peoples.wall_list - # Send the sprites to the display to be drawn - #controller.block_list - agv.sprite, - peoples.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - agv.wall_list, agv.gate_list - - controller.wall_list, controller.gate, controller.mines, - controller.charger, controller.wash, controller.filt, controller.store -> - display.static_sprites - - (display.tick)+ -> - controller.tick, - peoples.tick - - #Send pause player to game controller and ghosts + # Send the sprites to the display to be drawn + # controller.block_list + agv.sprite, peoples.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ -> agv.wall_list, agv.gate_list + + controller.wall_list, + controller.gate, + controller.mines, + controller.charger, + controller.wash, + controller.filt, + controller.store -> display.static_sprites + + (display.tick)+ -> controller.tick, peoples.tick + + # Send pause player to game controller and ghosts (agv.playerpause)+ -> peoples.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause agv.sprite -> controller.agv_sprite - + (peoples.sprite)+ -> controller.people_sprites, agv.people_sprites - + controller.score -> display.score peoples.icon_name, agv.icon_name -> display.icon_name display.icon -> peoples.icon, agv.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, agv.game_over, peoples.game_over - + + # controller.block_list -> agv.block_list (agv.restart)+ -> controller.restart, display.restart, peoples.restart - - #controller.block_list -> agv.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/BankedObserver.lf b/experiments/Python/src/Pac-Man/BankedObserver.lf index ba6b1057..63312e66 100644 --- a/experiments/Python/src/Pac-Man/BankedObserver.lf +++ b/experiments/Python/src/Pac-Man/BankedObserver.lf @@ -1,32 +1,33 @@ /** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . - * + * + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 **/ target Python { files: ["include/hbpacman.py", "include/images"] @@ -74,7 +75,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -88,9 +89,9 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites={=pacman.pygame.sprite.RenderPlain()=} state _top_corner_text - state _active(True) + state _active=True reaction(startup) {= dirname = os.path.dirname(__file__) @@ -194,7 +195,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BaseCharacter(width=0, height=0, image="images/Trollman.png", character_class={=pacman.Player=}) { input wall_list # Receive updated wall list input gate_list # Receive updated gate list input icon @@ -207,7 +208,7 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause={=False=} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -239,10 +240,10 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact # Should be replacable with an AI reactor Player extends BaseCharacter { timer pygame_event(0, 100 msec) - state _active(True) + state _active=True - state _speed(30) - state _boosted(False) + state _speed=30 + state _boosted=False reaction(pygame_event) -> sprite {= keyboard_events = pacman.pygame.event.get() @@ -292,12 +293,12 @@ reactor Player extends BaseCharacter { ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=})) extends BaseCharacter { +reactor Ghost (directions={=()=}) extends BaseCharacter { input tick #input pause # pause from player - state turn(0) - state steps(0) + state turn=0 + state steps=0 reaction(tick) -> sprite {= if self._pause is False: @@ -327,7 +328,7 @@ reactor Ghost (directions({=()=})) extends BaseCharacter { } #### Controller -reactor GameController(number_of_ghosts(4)) { +reactor GameController(number_of_ghosts=4) { output wall_list # List of walls on the map output gate output block_list # List of yummy dots for Pac-Man @@ -342,11 +343,11 @@ reactor GameController(number_of_ghosts(4)) { state _wall_list state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(2) - state _score(0) + state _block_list={=pacman.pygame.sprite.RenderPlain()=} + state _score_to_win=2 + state _score=0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) + state _pacman_collide={=pacman.pygame.sprite.RenderPlain()=} # state _controllerpause({=False=}) #not necessary reaction(startup, restart) -> wall_list, gate {= @@ -447,8 +448,8 @@ reactor GameController(number_of_ghosts(4)) { reactor Watcher { - state _pause(False) - state _active(True) + state _pause=False + state _active=True input game_over logical action delay @@ -486,7 +487,7 @@ main reactor { height = {= ghost_specs[bank_index]["height"] =}, image = {= ghost_specs[bank_index]["image"] =}, directions = {= ghost_specs[bank_index]["directions"] =}, - character_class = ({=pacman.Ghost=}) + character_class = {=pacman.Ghost=} ) observer = new Watcher() diff --git a/experiments/Python/src/Pac-Man/ContainedPlayer.lf b/experiments/Python/src/Pac-Man/ContainedPlayer.lf index eb3f6fec..ccd097b9 100644 --- a/experiments/Python/src/Pac-Man/ContainedPlayer.lf +++ b/experiments/Python/src/Pac-Man/ContainedPlayer.lf @@ -1,10 +1,7 @@ -/* Reactors used for contained version of - * player AI in PacManWFSM.lf - */ - +/** Reactors used for contained version of player AI in PacManWFSM.lf */ target Python { files: ["include/AIPacSupport.py", "include/hbpacman.py"] -}; +} preamble {= # must import some file for setup functions @@ -13,11 +10,15 @@ preamble {= =} ### Ticker Reactor -reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - timer ticker(0, 100msec) - - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor Ticker( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + timer ticker(0, 100 msec) + + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input[4] ghost_sprites input icon input eat_result @@ -25,22 +26,22 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas input chase_result input game_over input character - + state character_instance state _wall_list state _gate_list - state _ghosts({=[]=}) - state _pause(False) - state _active(True) - state _ai_control(True) - state _layout({=pacman.walls=}) + state _ghosts = {= [] =} + state _pause = False + state _active = True + state _ai_control = True + state _layout = {= pacman.walls =} output[7] result output sprite output icon_name output playerpause output restart - + reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) @@ -55,13 +56,13 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas self._wall_list = wall_list.value self._gate_list = gate_list.value =} - + reaction(ticker) -> playerpause, restart, sprite, result {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -71,7 +72,7 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = False self._active = True print(self.character_instance.rect.left) - + if event.key == pacman.pygame.K_SPACE: if self._pause is False: self._pause = True @@ -90,8 +91,8 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, -30) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -118,9 +119,8 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas port.set(self._layout) elif (i - 3) < len(self._ghosts): port.set(self._ghosts[i - 3]) - =} - + reaction(ghost_sprites) {= self._ghosts = [] for ghost in ghost_sprites: @@ -137,7 +137,7 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas elif eat_result.is_present: self.character_instance = eat_result.value =} - + reaction(game_over) {= self._active = False self._pause = True @@ -149,7 +149,7 @@ reactor Ticker(width(0), height(0), image("images/Trollman.png"), character_clas reactor Close { input[7] tick output[7] result - + reaction(tick) -> result {= all_present = True #result_bool = True @@ -176,16 +176,15 @@ reactor Close { port.set(tick[i].value) #result.set(tick) =} - } ### Ghost Scared? Condition Reactor reactor Scared { input[7] tick input frenzy - state _frenzy(False) + state _frenzy = False output[7] result - + reaction(tick) -> result {= all_present = True for i, port in enumerate(tick): @@ -212,7 +211,6 @@ reactor Scared { reaction(frenzy) {= self._frenzy = frenzy.value =} - } ### Chase Ghost Action Reactor @@ -220,7 +218,7 @@ reactor Chase { input[7] tick state character_instance output result - + reaction(tick) -> result {= if tick[0].is_present and tick[0].value: all_present = True @@ -240,12 +238,11 @@ reactor Chase { } ### Avoid Ghost Action Reactor - reactor Avoid { input[7] tick state character_instance output result - + reaction(tick) -> result {= print("avoid got tick") if tick[0].is_present and not tick[0].value: @@ -267,7 +264,6 @@ reactor Avoid { } ### Eat Pills Action Reactor - reactor Eat { input[7] tick input block_list @@ -275,14 +271,14 @@ reactor Eat { input sprite state _wall_list state _block_list - state _eat_moves(0) - state character_instance + state _eat_moves = 0 + state character_instance output result reaction(wall_list) {= self._wall_list = wall_list.value =} - + reaction(tick) block_list -> result {= if tick[0].is_present and not tick[0].value: all_present = True @@ -306,7 +302,6 @@ reactor Eat { result.set(self.character_instance) =} - reaction(sprite) {= #print("received sprite") self.character_instance = sprite.value @@ -315,7 +310,4 @@ reactor Eat { reaction(block_list) {= self._block_list = block_list.value =} - } - - diff --git a/experiments/Python/src/Pac-Man/Foomba.lf b/experiments/Python/src/Pac-Man/Foomba.lf index 9347afda..4fd277f7 100644 --- a/experiments/Python/src/Pac-Man/Foomba.lf +++ b/experiments/Python/src/Pac-Man/Foomba.lf @@ -1,36 +1,38 @@ - /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman +/** + * A simple Pacman game. Source: + * + * https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py"] -}; +} preamble {= import os @@ -76,7 +78,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +86,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +117,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.white) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.white) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -136,15 +138,15 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.black) self._screen.blit(self._top_corner_text, [10, 10]) @@ -158,7 +160,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) @@ -170,7 +172,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("Paused. Press SPACE to continue,", True, pacman.black) self._screen.blit(text2, [135, 303]) text3=self._font.render("Press M to toggle AI.", True, pacman.black) @@ -178,43 +180,44 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - if game_over.value: - text1=self._font.render("Area Cleared!", True, pacman.black) - else: - text1=self._font.render("Collision detected.", True, pacman.black) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER.", True, pacman.black) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.black) - self._screen.blit(text3, [165, 333]) - - pacman.pygame.display.flip() - + if game_over.is_present: + self._game_over = True + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + if game_over.value: + text1=self._font.render("Area Cleared!", True, pacman.black) + else: + text1=self._font.render("Collision detected.", True, pacman.black) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER.", True, pacman.black) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.black) + self._screen.blit(text3, [165, 333]) + + pacman.pygame.display.flip() =} reaction(restart) {= self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/user.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -223,7 +226,7 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -239,16 +242,19 @@ reactor BaseCharacter(width(0), height(0), image("images/user.png"), character_c self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon @@ -258,52 +264,55 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon - state _frenzy(False) - state _ghosts({=[]=}) - state _layout({=pacman.walls=}) - state _block_list({=[]=}) - state _ai_control(True) + state _frenzy = False + state _ghosts = {= [] =} + state _layout = {= pacman.walls =} + state _block_list = {= [] =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - state _eat_moves(0) - state _avoid_moves(0) + state _pause = {= False =} + state _resetting = False + state _eat_moves = 0 + state _avoid_moves = 0 output sprite output icon_name output playerpause output restart - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value @@ -317,7 +326,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: print("detecting key down") if event.key == pacman.pygame.K_ESCAPE: @@ -345,8 +354,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, -30) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -370,7 +379,6 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas if self._ai_control: print("changing to close") Close.set() - =} reaction(ghost_sprites) {= @@ -393,9 +401,9 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = True self.character_instance.speedzero() =} + } - } mode Close { - + mode Close { reaction(reset) -> reset(Eat), reset(Avoid), history(export) {= print("in mode close") if len(self._block_list) > 0 and len(self._ghosts) > 0: @@ -407,46 +415,45 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas else: export.set() =} + } - } mode Eat { - + mode Eat { reaction(reset) -> history(export) {= print("in mode eat") self.character_instance.ai_eat(pacman.walls, self._ghosts, self._block_list, self._eat_moves) self._eat_moves = self.character_instance.get_num_moves() export.set() =} - - } mode Avoid { + } + mode Avoid { reaction(reset) -> history(export) {= print("avoid time") self.character_instance.ai_avoid(self._layout, self._ghosts, 7) #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} - } } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + reaction(playerpause) {= - if playerpause.is_present: - self._pause = playerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -475,7 +482,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { ) sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -487,40 +494,39 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy input[number_of_ghosts] ghost_sprites input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + reaction(startup) -> wall_list, gate {= _all_sprites_list = pacman.pygame.sprite.RenderPlain() self._wall_list = pacman.setupRoomFoomba(_all_sprites_list) @@ -528,18 +534,17 @@ reactor GameController(number_of_ghosts(4)) { wall_list.set(self._wall_list) gate.set(self._gate) - =} - + reaction(pacman_sprite) {= self._pacman_collide.empty() self._pacman_collide.add(pacman_sprite.value) self._pacman_sprite = pacman_sprite.value =} - + reaction(startup) -> block_list {= # Draw the grid - + for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -552,16 +557,16 @@ reactor GameController(number_of_ghosts(4)) { b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) block_list.set(block) # Send it to be drawn - + self._score_to_win = len(self._block_list) print(self._energizer_list) print(self._score_to_win) @@ -569,14 +574,14 @@ reactor GameController(number_of_ghosts(4)) { reaction(pacman_sprite) -> score, game_over {= blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - + # Check the list of collisions. if len(blocks_hit_list) > 0: self._score +=len(blocks_hit_list) if self._score == self._score_to_win: game_over.set(True) - + score.set(self._score) =} @@ -586,7 +591,7 @@ reactor GameController(number_of_ghosts(4)) { for ghost in ghost_sprites: if ghost.is_present: monsta_list.add(ghost.value) - + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) if monsta_hit_list and not self._frenzy: @@ -594,14 +599,13 @@ reactor GameController(number_of_ghosts(4)) { elif monsta_hit_list and self._frenzy: for monsta in monsta_hit_list: monsta.moveoffgrid() - =} # Send the updated blocks - reaction(tick) -> block_list {= + reaction(tick) -> block_list {= block_list.set(self._block_list) =} - + reaction(restart) {= if restart.value: self._block_list = pacman.pygame.sprite.RenderPlain() @@ -611,19 +615,19 @@ reactor GameController(number_of_ghosts(4)) { if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): continue block = pacman.Block(pacman.brown, 6, 6) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) self._score_to_win = len(self._block_list) @@ -631,11 +635,10 @@ reactor GameController(number_of_ghosts(4)) { self._frenzy = False self._score = 0 =} - + reaction(shutdown) {= pacman.pygame.quit() =} - } main reactor { @@ -643,61 +646,51 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/roomb1.png") - #width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/roomb1.png") + + # width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + (ghosts.sprite)+ -> controller.ghost_sprites, player.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - + controller.block_list -> player.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/KielPacManTree.lf b/experiments/Python/src/Pac-Man/KielPacManTree.lf index 3bc96ee9..57c0e85a 100644 --- a/experiments/Python/src/Pac-Man/KielPacManTree.lf +++ b/experiments/Python/src/Pac-Man/KielPacManTree.lf @@ -1,7 +1,7 @@ -/** PacMan Dresden Reactor **/ -target Python{ +/** PacMan Dresden Reactor * */ +target Python { files: ["include/hbpacman.py", "include/AIPacSupport.py"] -}; +} preamble {= import hbpacman as pacman @@ -32,13 +32,11 @@ reactor EatPills extends BehaviorNode { input pacman_sprite input block_list input init_sprite - - #output sprite - state character_instance - state _ghosts({=[]=}) - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _eat_moves(0) + state character_instance # output sprite + state _ghosts = {= [] =} + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _eat_moves = 0 reaction(start) -> success, failure {= print("eat ghosts leng is ", len(self._ghosts)) @@ -72,17 +70,14 @@ reactor EatPills extends BehaviorNode { else: self.character_instance = init_sprite.value =} - } reactor AvoidGhost extends BehaviorNode { input pacman_sprite input[4] ghost_sprites - #output sprite - - state character_instance - state _ghosts({=[]=}) + state character_instance # output sprite + state _ghosts = {= [] =} reaction(start) -> success, failure {= if len(self._ghosts) > 0: @@ -92,7 +87,7 @@ reactor AvoidGhost extends BehaviorNode { else: failure.set(True) =} - + reaction(pacman_sprite) {= self.character_instance = pacman_sprite.value =} @@ -109,10 +104,8 @@ reactor ChaseGhost extends BehaviorNode { input pacman_sprite input[4] ghost_sprites - #output sprite - - state character_instance - state _ghosts({=[]=}) + state character_instance # output sprite + state _ghosts = {= [] =} reaction(start) -> success, failure {= if len(self._ghosts) > 0: @@ -139,10 +132,8 @@ reactor GhostClose extends BehaviorNode { input pacman_sprite input[4] ghost_sprites - #output sprite - - state character_instance - state _ghosts({=[]=}) + state character_instance # output sprite + state _ghosts = {= [] =} reaction(start) -> success, failure {= if len(self._ghosts) > 0 and len(self._ghosts) > 0 and self.character_instance is not None: @@ -168,7 +159,7 @@ reactor GhostClose extends BehaviorNode { reactor GhostScared extends BehaviorNode { input frenzy - state _frenzy(False) + state _frenzy = False reaction(start) -> success, failure {= if self._frenzy: @@ -180,11 +171,14 @@ reactor GhostScared extends BehaviorNode { reaction(frenzy) {= self._frenzy = frenzy.value =} - } -#fallback -reactor PacMan0(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) extends BehaviorNode { +# fallback +reactor PacMan0( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) extends BehaviorNode { left = new PacMan1() right = new EatPills() merge = new MergeOr() @@ -196,10 +190,6 @@ reactor PacMan0(width(0), height(0), image("images/Trollman.png"), character_cla input icon input init_sprite - reaction(icon) {= - self.character_instance = self.character_class(self.width, self.height, icon.value) - =} - start -> left.start (ghost_sprites)+ -> right.ghost_sprites, left.ghost_sprites (pacman_sprite)+ -> right.pacman_sprite, left.pacman_sprite @@ -212,8 +202,13 @@ reactor PacMan0(width(0), height(0), image("images/Trollman.png"), character_cla right.success -> merge.right right.failure -> failure merge.merged -> success + + reaction(icon) {= + self.character_instance = self.character_class(self.width, self.height, icon.value) + =} } -#sequence + +# sequence reactor PacMan1 extends BehaviorNode { left = new GhostClose() right = new PacMan2() @@ -234,7 +229,7 @@ reactor PacMan1 extends BehaviorNode { merge.merged -> failure } -#fallback +# fallback reactor PacMan2 extends BehaviorNode { left = new PacMan3() right = new AvoidGhost() @@ -256,7 +251,7 @@ reactor PacMan2 extends BehaviorNode { merge.merged -> success } -#sequence +# sequence reactor PacMan3 extends BehaviorNode { left = new GhostScared() right = new ChaseGhost() diff --git a/experiments/Python/src/Pac-Man/PacMan.lf b/experiments/Python/src/Pac-Man/PacMan.lf index bd30a189..bb763788 100644 --- a/experiments/Python/src/Pac-Man/PacMan.lf +++ b/experiments/Python/src/Pac-Man/PacMan.lf @@ -1,36 +1,36 @@ /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman + * A simple Pacman game. Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images"] -}; +} preamble {= import os @@ -40,7 +40,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -49,19 +49,21 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p output tick output[5] icon - + state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text + timer pygame_tick(0, 100 msec) # 10 FPS + reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -69,22 +71,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -100,7 +100,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -120,7 +120,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - + reaction(game_over) {= #Grey background w = pacman.pygame.Surface((400,200)) # the size of your rect @@ -143,9 +143,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -169,13 +173,13 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ## Player # Should be replacable with an AI reactor Player extends BaseCharacter { timer pygame_event(0, 100 msec) + reaction(pygame_event) -> sprite {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: @@ -200,7 +204,7 @@ reactor Player extends BaseCharacter { self.character_instance.changespeed(0, 30) if event.key == pacman.pygame.K_DOWN: self.character_instance.changespeed(0, -30) - + self.character_instance.update( self._wall_list, self._gate_list @@ -211,10 +215,10 @@ reactor Player extends BaseCharacter { ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=})) extends BaseCharacter { - input tick - state turn(0) - state steps(0) +reactor Ghost(directions = {= () =}) extends BaseCharacter { + input tick + state turn = 0 + state steps = 0 reaction(tick) -> sprite {= returned = self.character_instance.changespeed( @@ -239,29 +243,28 @@ reactor Ghost (directions({=()=})) extends BaseCharacter { ) sprite.set(self.character_instance) =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over input[number_of_ghosts] ghost_sprites input pacman_sprite - input tick # The game tick + input tick # The game tick state _wall_list state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + reaction(startup) -> wall_list, gate {= _all_sprites_list = pacman.pygame.sprite.RenderPlain() self._wall_list = pacman.setupRoomOne(_all_sprites_list) @@ -269,22 +272,21 @@ reactor GameController(number_of_ghosts(4)) { wall_list.set(self._wall_list) gate.set(self._gate) - =} - + reaction(pacman_sprite) {= self._pacman_collide.empty() self._pacman_collide.add(pacman_sprite.value) self._pacman_sprite = pacman_sprite.value =} - + reaction(startup) -> block_list {= # Draw the grid for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): continue - + block = pacman.Block(pacman.yellow, 4, 4) # Set a random location for the block @@ -293,12 +295,12 @@ reactor GameController(number_of_ghosts(4)) { b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) block_list.set(block) # Send it to be drawn @@ -316,7 +318,7 @@ reactor GameController(number_of_ghosts(4)) { game_over.set("Won!") request_stop() - + score.set(self._score) =} @@ -326,21 +328,19 @@ reactor GameController(number_of_ghosts(4)) { for ghost in ghost_sprites: if ghost.is_present: monsta_list.add(ghost.value) - + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) if monsta_hit_list: game_over.set("Lost!") request_stop() - =} # Send the updated blocks - reaction(tick) -> block_list {= + reaction(tick) -> block_list {= block_list.set(self._block_list) =} - - + reaction(shutdown) {= pacman.pygame.quit() =} @@ -351,89 +351,59 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + # Ghosts pinky = new Ghost( - width = {=pacman.w=}, - height = {=pacman.m_h=}, - image = "images/Pinky.png", - directions = {=pacman.Pinky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.w =}, + height = {= pacman.m_h =}, + image="images/Pinky.png", + directions = {= pacman.Pinky_directions =}, + character_class = {= pacman.Ghost =}) blinky = new Ghost( - width = {=pacman.w=}, - height = {=pacman.b_h=}, - image = "images/Blinky.png", - directions = {=pacman.Blinky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.w =}, + height = {= pacman.b_h =}, + image="images/Blinky.png", + directions = {= pacman.Blinky_directions =}, + character_class = {= pacman.Ghost =}) inky = new Ghost( - width = {=pacman.i_w=}, - height = {=pacman.m_h=}, - image = "images/Inky.png", - directions = {=pacman.Inky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.i_w =}, + height = {= pacman.m_h =}, + image="images/Inky.png", + directions = {= pacman.Inky_directions =}, + character_class = {= pacman.Ghost =}) clyde = new Ghost( - width = {=pacman.c_w=}, - height = {=pacman.m_h=}, - image = "images/Clyde.png", - directions = {=pacman.Clyde_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.c_w =}, + height = {= pacman.m_h =}, + image="images/Clyde.png", + directions = {= pacman.Clyde_directions =}, + character_class = {= pacman.Ghost =}) ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls - (controller.wall_list)+ -> - pinky.wall_list, - blinky.wall_list, - inky.wall_list, - clyde.wall_list + (controller.wall_list)+ -> pinky.wall_list, blinky.wall_list, inky.wall_list, clyde.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - pinky.sprite, - blinky.sprite, - inky.sprite, - clyde.sprite -> - display.moving_sprites + controller.block_list, player.sprite, pinky.sprite, blinky.sprite, inky.sprite, clyde.sprite + -> display.moving_sprites controller.game_over -> display.game_over - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - (display.tick)+ -> - controller.tick, - pinky.tick, - blinky.tick, - inky.tick, - clyde.tick + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, pinky.tick, blinky.tick, inky.tick, clyde.tick player.sprite -> controller.pacman_sprite - - pinky.sprite, - blinky.sprite, - inky.sprite, - clyde.sprite-> controller.ghost_sprites - - controller.score -> display.score - pinky.icon_name, - blinky.icon_name, - inky.icon_name, - clyde.icon_name, - player.icon_name -> display.icon_name + pinky.sprite, blinky.sprite, inky.sprite, clyde.sprite -> controller.ghost_sprites + + controller.score -> display.score - display.icon -> - pinky.icon, - blinky.icon, - inky.icon, - clyde.icon, player.icon + pinky.icon_name, blinky.icon_name, inky.icon_name, clyde.icon_name, player.icon_name + -> display.icon_name + display.icon -> pinky.icon, blinky.icon, inky.icon, clyde.icon, player.icon } diff --git a/experiments/Python/src/Pac-Man/PacManKielTree.lf b/experiments/Python/src/Pac-Man/PacManKielTree.lf index dbf69d9e..09dfdbe4 100644 --- a/experiments/Python/src/Pac-Man/PacManKielTree.lf +++ b/experiments/Python/src/Pac-Man/PacManKielTree.lf @@ -1,36 +1,34 @@ - /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman - * LICENSE: N/A +/** + * A simple Pacman game. Source: https://github.com/hbokmann/Pacman LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py", "KielPacManTree.lf"] -}; +} import PacMan0 from "KielPacManTree.lf" @@ -42,7 +40,7 @@ preamble {= sys.path.append(curr_dirname) import hbpacman as pacman import AIPacSupport as ai - + # Construct a table of ghost characteristics to access # using the bank member as the index. @@ -78,11 +76,8 @@ preamble {= ] =} - - - #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -90,28 +85,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -119,22 +116,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -145,16 +140,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) - =} - + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) + =} + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -172,36 +167,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): #print("got pacman") sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("Paused. Press SPACE to continue,", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("M for Manual, and R for Robot.", True, pacman.white) @@ -209,57 +203,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -271,25 +267,28 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon @@ -299,37 +298,40 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon input initial_sprite input pacman_sprite - state _frenzy(False) - state _ghosts({=[]=}) - state _layout({=pacman.walls=}) - state _block_list({=[]=}) - state _ai_control(True) + state _frenzy = False + state _ghosts = {= [] =} + state _layout = {= pacman.walls =} + state _block_list = {= [] =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - state _eat_moves(0) - state _avoid_moves(0) + state _pause = {= False =} + state _resetting = False + state _eat_moves = 0 + state _avoid_moves = 0 output sprite output init_sprite @@ -337,15 +339,14 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas output playerpause output restart - bt = new PacMan0(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") + bt = new PacMan0(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") frenzy -> bt.frenzy ghost_sprites -> bt.ghost_sprites initial_sprite -> bt.init_sprite block_list -> bt.block_list pacman_sprite -> bt.pacman_sprite - icon -> bt.icon + icon -> bt.icon bt.success -> sprite - reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -361,7 +362,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._wall_list = wall_list.value self._gate_list = gate_list.value =} - + reaction(pygame_event) -> bt.start, playerpause, restart {= if not self._pause and self._active and self._ai_control: #bt.pacman_sprite.set(self.character_instance) @@ -371,7 +372,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -392,9 +393,9 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas if event.key == pacman.pygame.K_r: self._pause = False self._ai_control = True - playerpause.set(self._pause) - =} - + playerpause.set(self._pause) + =} + reaction(ghost_sprites) {= self._ghosts = [] for ghost in ghost_sprites: @@ -415,33 +416,31 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = True self.character_instance.speedzero() =} - } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - - #reaction(startup) {= - # self.image = "images/pacman.png" + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + + # reaction(startup) {= + # self.image = "images/pacman.png" #=} - reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -473,7 +472,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -485,69 +484,153 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart input init_sprite - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite, init_sprite) {= - print("got pacman sprite") - if pacman_sprite.is_present: - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - else: - self._pacman_collide.empty() - self._pacman_collide.add(init_sprite.value) - self._pacman_sprite = init_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite, init_sprite) {= + print("got pacman sprite") + if pacman_sprite.is_present: + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + else: + self._pacman_collide.empty() + self._pacman_collide.add(init_sprite.value) + self._pacman_sprite = init_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite, init_sprite) -> score, game_over, frenzy, end_frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) > 0: + self._frenzy = True + print(self._frenzy) + frenzy.set(self._frenzy) + delay = randint(3, 7) + end_frenzy.schedule(SEC(delay)) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list and not self._frenzy: + game_over.set("Lost!") + elif monsta_hit_list and self._frenzy: + for monsta in monsta_hit_list: + monsta.moveoffgrid() + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -557,126 +640,37 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite, init_sprite) -> score, game_over, frenzy, end_frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) > 0: - self._frenzy = True - print(self._frenzy) - frenzy.set(self._frenzy) - delay = randint(3, 7) - end_frenzy.schedule(SEC(delay)) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list and not self._frenzy: - game_over.set("Lost!") - elif monsta_hit_list and self._frenzy: - for monsta in monsta_hit_list: - monsta.moveoffgrid() - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._frenzy = False - self._score = 0 - =} - - reaction(end_frenzy) -> frenzy {= self._frenzy = False - print(self._frenzy, " and times up") - frenzy.set(self._frenzy) - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} + self._score = 0 + =} - # } + reaction(end_frenzy) -> frenzy {= + self._frenzy = False + print(self._frenzy, " and times up") + frenzy.set(self._frenzy) + =} + + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -684,64 +678,54 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - #width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + + # width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause (player.sprite)+ -> controller.pacman_sprite, player.pacman_sprite (player.init_sprite)+ -> controller.init_sprite, player.initial_sprite - + (ghosts.sprite)+ -> controller.ghost_sprites, player.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - + (controller.frenzy)+ -> player.frenzy, ghosts.frenzy - + controller.block_list -> player.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/PacManNMBT.lf b/experiments/Python/src/Pac-Man/PacManNMBT.lf index e5a22bd9..8a39f4b0 100644 --- a/experiments/Python/src/Pac-Man/PacManNMBT.lf +++ b/experiments/Python/src/Pac-Man/PacManNMBT.lf @@ -1,42 +1,46 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 . + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py"] -}; +} + import Close from "ContainedPlayer.lf" import Eat from "ContainedPlayer.lf" import Scared from "ContainedPlayer.lf" import Avoid from "ContainedPlayer.lf" import Chase from "ContainedPlayer.lf" import Ticker from "ContainedPlayer.lf" + preamble {= import os #import pyautogui @@ -44,7 +48,7 @@ preamble {= curr_dirname = os.path.dirname(__file__) sys.path.append(curr_dirname) import hbpacman as pacman - + #may need to import additional python files to support containedplayer # Construct a table of ghost characteristics to access @@ -82,7 +86,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -90,28 +94,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick - output[5] icon - - state _game_over(False) + output[5] icon + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -119,22 +125,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -145,16 +149,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -168,36 +172,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("The game is paused.", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("To continue, hit SPACE.", True, pacman.white) @@ -205,57 +208,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - #pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - #pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + #pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + #pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -267,24 +272,27 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon @@ -294,35 +302,38 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI #) -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon - + state character_instance - + output sprite output icon_name output playerpause output restart - - ticker = new Ticker(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") + + ticker = new Ticker(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") c1 = new Close() a1 = new Eat() c2 = new Scared() a2 = new Avoid() a3 = new Chase() - + (wall_list)+ -> ticker.wall_list, a1.wall_list icon -> ticker.icon gate_list -> ticker.gate_list @@ -331,7 +342,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas block_list -> a1.block_list (ghost_sprites)+ -> ticker.ghost_sprites (ticker.sprite)+ -> sprite, a1.sprite - ticker.icon_name -> icon_name + ticker.icon_name -> icon_name ticker.playerpause -> playerpause ticker.restart -> restart ticker.result -> c1.tick @@ -340,33 +351,31 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas a1.result -> ticker.eat_result a2.result -> ticker.chase_result a3.result -> ticker.avoid_result - -} +} ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - - #reaction(startup) {= - # self.image = "images/pacman.png" + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + + # reaction(startup) {= + # self.image = "images/pacman.png" #=} - reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -398,7 +407,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -410,62 +419,146 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite) {= - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite) {= + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) > 0: + self._frenzy = True + print(self._frenzy) + frenzy.set(self._frenzy) + delay = randint(3, 7) + end_frenzy.schedule(SEC(delay)) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list and not self._frenzy: + game_over.set("Lost!") + elif monsta_hit_list and self._frenzy: + for monsta in monsta_hit_list: + monsta.moveoffgrid() + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -475,126 +568,37 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) > 0: - self._frenzy = True - print(self._frenzy) - frenzy.set(self._frenzy) - delay = randint(3, 7) - end_frenzy.schedule(SEC(delay)) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list and not self._frenzy: - game_over.set("Lost!") - elif monsta_hit_list and self._frenzy: - for monsta in monsta_hit_list: - monsta.moveoffgrid() - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._frenzy = False - self._score = 0 - =} - - reaction(end_frenzy) -> frenzy {= self._frenzy = False - print(self._frenzy, " and times up") - frenzy.set(self._frenzy) - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} + self._score = 0 + =} - # } + reaction(end_frenzy) -> frenzy {= + self._frenzy = False + print(self._frenzy, " and times up") + frenzy.set(self._frenzy) + =} + + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -602,65 +606,54 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - #width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + + # width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + (ghosts.sprite)+ -> controller.ghost_sprites, player.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - + (controller.frenzy)+ -> player.frenzy, ghosts.frenzy - + + # ghosts.sprite -> player.ghost_sprites controller.block_list -> player.block_list - - #ghosts.sprite -> player.ghost_sprites - } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/PacManTesting.lf b/experiments/Python/src/Pac-Man/PacManTesting.lf index 9d8a8f02..59c5f7af 100644 --- a/experiments/Python/src/Pac-Man/PacManTesting.lf +++ b/experiments/Python/src/Pac-Man/PacManTesting.lf @@ -1,36 +1,37 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * 1- lfc src/PacMan.lf 2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py"] -}; +} preamble {= import os @@ -75,7 +76,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -83,28 +84,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -112,22 +115,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -138,16 +139,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) - =} - + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) + =} + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -161,36 +162,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("The game is paused.", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("To continue, hit SPACE.", True, pacman.white) @@ -198,57 +198,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -260,35 +262,37 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input frenzy output playerpause output restart - - state _speed(30) - state _frenzy(False) - - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + state _speed = 30 + state _frenzy = False + + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -297,9 +301,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - + state _pause = {= False =} + state _resetting = False reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -315,13 +318,13 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._wall_list = wall_list.value self._gate_list = gate_list.value =} - + reaction(pygame_event) -> sprite, playerpause, restart {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -331,14 +334,14 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = False self._active = True print(self.character_instance.rect.left) - + if event.key == pacman.pygame.K_SPACE and self._active: if self._pause is False: self._pause = True else: self._pause = False print(self._pause) - elif self._pause is False: + elif self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed * -1, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -347,8 +350,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, self._speed) - - if event.type == pacman.pygame.KEYUP and self._pause is False: + + if event.type == pacman.pygame.KEYUP and self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -359,7 +362,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_b: self._speed -= 10 - + if self._pause is False: self.character_instance.update( self._wall_list, @@ -368,14 +371,14 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas sprite.set(self.character_instance) playerpause.set(self._pause) =} - + reaction(game_over) {= - self._active = False - self._pause = True - self.character_instance.speedzero() -# ending.schedule(MSEC(500)) + self._active = False + self._pause = True + self.character_instance.speedzero() + # ending.schedule(MSEC(500)) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} @@ -383,28 +386,27 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - - #reaction(startup) {= - # self.image = "images/pacman.png" + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + + # reaction(startup) {= + # self.image = "images/pacman.png" #=} - reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -436,7 +438,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -448,62 +450,147 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite) {= - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite) {= + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) > 0: + self._frenzy = True + print(self._frenzy) + frenzy.set(self._frenzy) + delay = randint(3, 7) + end_frenzy.schedule(SEC(delay)) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list and not self._frenzy: + game_over.set("Lost!") + elif monsta_hit_list and self._frenzy: + for monsta in monsta_hit_list: + monsta.moveoffgrid() + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -513,127 +600,37 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) > 0: - self._frenzy = True - print(self._frenzy) - frenzy.set(self._frenzy) - delay = randint(3, 7) - end_frenzy.schedule(SEC(delay)) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list and not self._frenzy: - game_over.set("Lost!") - elif monsta_hit_list and self._frenzy: - for monsta in monsta_hit_list: - monsta.moveoffgrid() - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._frenzy = False - self._score = 0 - =} - - reaction(end_frenzy) -> frenzy {= self._frenzy = False - print(self._frenzy, " and times up") - frenzy.set(self._frenzy) - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} + self._score = 0 + =} + + reaction(end_frenzy) -> frenzy {= + self._frenzy = False + print(self._frenzy, " and times up") + frenzy.set(self._frenzy) + =} - # } + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -641,61 +638,50 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + ghosts.sprite -> controller.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - - (controller.frenzy)+ -> player.frenzy, ghosts.frenzy - + (controller.frenzy)+ -> player.frenzy, ghosts.frenzy } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/PacManWFrenzy.lf b/experiments/Python/src/Pac-Man/PacManWFrenzy.lf index 750ec402..705e3dbf 100644 --- a/experiments/Python/src/Pac-Man/PacManWFrenzy.lf +++ b/experiments/Python/src/Pac-Man/PacManWFrenzy.lf @@ -1,36 +1,33 @@ - /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman - * LICENSE: N/A +/** + * A simple Pacman game. Source: https://github.com/hbokmann/Pacman LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py"] -}; +} preamble {= import os @@ -75,7 +72,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -83,28 +80,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -112,22 +111,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -138,16 +135,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) - =} - + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) + =} + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -161,36 +158,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("The game is paused.", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("To continue, hit SPACE.", True, pacman.white) @@ -198,57 +194,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -260,24 +258,27 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon @@ -287,26 +288,28 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input frenzy output playerpause output restart - - state _speed(30) - state _frenzy(False) - - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + state _speed = 30 + state _frenzy = False + + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -315,9 +318,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - + state _pause = {= False =} + state _resetting = False reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -333,13 +335,13 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._wall_list = wall_list.value self._gate_list = gate_list.value =} - + reaction(pygame_event) -> sprite, playerpause, restart {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -349,14 +351,14 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = False self._active = True print(self.character_instance.rect.left) - + if event.key == pacman.pygame.K_SPACE and self._active: if self._pause is False: self._pause = True else: self._pause = False print(self._pause) - elif self._pause is False: + elif self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed * -1, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -365,8 +367,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, self._speed) - - if event.type == pacman.pygame.KEYUP and self._pause is False: + + if event.type == pacman.pygame.KEYUP and self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -377,7 +379,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_b: self._speed -= 10 - + if self._pause is False: self.character_instance.update( self._wall_list, @@ -386,14 +388,14 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas sprite.set(self.character_instance) playerpause.set(self._pause) =} - + reaction(game_over) {= - self._active = False - self._pause = True - self.character_instance.speedzero() -# ending.schedule(MSEC(500)) + self._active = False + self._pause = True + self.character_instance.speedzero() + # ending.schedule(MSEC(500)) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} @@ -401,28 +403,27 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - - #reaction(startup) {= - # self.image = "images/pacman.png" + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + + # reaction(startup) {= + # self.image = "images/pacman.png" #=} - reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -454,7 +455,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -466,62 +467,147 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite) {= - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite) {= + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) > 0: + self._frenzy = True + print(self._frenzy) + frenzy.set(self._frenzy) + delay = randint(3, 7) + end_frenzy.schedule(SEC(delay)) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list and not self._frenzy: + game_over.set("Lost!") + elif monsta_hit_list and self._frenzy: + for monsta in monsta_hit_list: + monsta.moveoffgrid() + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -531,127 +617,37 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) > 0: - self._frenzy = True - print(self._frenzy) - frenzy.set(self._frenzy) - delay = randint(3, 7) - end_frenzy.schedule(SEC(delay)) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list and not self._frenzy: - game_over.set("Lost!") - elif monsta_hit_list and self._frenzy: - for monsta in monsta_hit_list: - monsta.moveoffgrid() - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._frenzy = False - self._score = 0 - =} - - reaction(end_frenzy) -> frenzy {= self._frenzy = False - print(self._frenzy, " and times up") - frenzy.set(self._frenzy) - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} + self._score = 0 + =} + + reaction(end_frenzy) -> frenzy {= + self._frenzy = False + print(self._frenzy, " and times up") + frenzy.set(self._frenzy) + =} - # } + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -659,62 +655,52 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - #width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + + # width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + (ghosts.sprite)+ -> controller.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - + + # controller.block_list -> player.block_list (controller.frenzy)+ -> player.frenzy, ghosts.frenzy - - #controller.block_list -> player.block_list } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/PacManWModalBT.lf b/experiments/Python/src/Pac-Man/PacManWModalBT.lf index 8fa5ed1c..d5f028c5 100644 --- a/experiments/Python/src/Pac-Man/PacManWModalBT.lf +++ b/experiments/Python/src/Pac-Man/PacManWModalBT.lf @@ -1,36 +1,37 @@ - /** +/** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. - * S + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. + * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. * -**/ + * See https://arxiv.org/abs/2109.07771 . + */ target Python { files: ["include/hbpacman.py", "include/images", "include/AIPacSupport.py"] -}; +} preamble {= import os @@ -76,7 +77,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -84,28 +85,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -113,22 +116,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -139,16 +140,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -162,36 +163,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("Paused. Press SPACE to continue,", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("M for Manual, and R for Robot.", True, pacman.white) @@ -199,57 +199,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -261,25 +263,28 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } ###Base Player -reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor BasePlayer( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon @@ -289,52 +294,55 @@ reactor BasePlayer(width(0), height(0), image("images/Trollman.png"), character_ output icon_name output playerpause output restart - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over input frenzy input[4] ghost_sprites - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input block_list input icon - state _frenzy(False) - state _ghosts({=[]=}) - state _layout({=pacman.walls=}) - state _block_list({=[]=}) - state _ai_control(True) + state _frenzy = False + state _ghosts = {= [] =} + state _layout = {= pacman.walls =} + state _block_list = {= [] =} + state _ai_control = True state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - state _eat_moves(0) - state _avoid_moves(0) + state _pause = {= False =} + state _resetting = False + state _eat_moves = 0 + state _avoid_moves = 0 output sprite output icon_name output playerpause output restart - + initial mode export { reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) icon_name.set(os.path.join(dirname, self.image)) =} - + reaction(icon) -> sprite {= self.character_instance = self.character_class(self.width, self.height, icon.value) sprite.set(self.character_instance) =} - + reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value @@ -345,7 +353,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -375,8 +383,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, -30) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, 30) - - if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: + + if event.type == pacman.pygame.KEYUP and not self._ai_control and not self._pause and self._active: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(30, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -398,7 +406,6 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas if not self._pause and self._active: if self._ai_control: Close.set() - =} reaction(ghost_sprites) {= @@ -421,9 +428,9 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._pause = True self.character_instance.speedzero() =} + } - } mode Close { - + mode Close { reaction(reset) -> reset(Eat), reset(Scared), history(export) {= if len(self._block_list) > 0 and len(self._ghosts) > 0: if len(ai.closeghostdist(self._layout, self._ghosts, self.character_instance.rect.left, self.character_instance.rect.top, 6)) > 5: @@ -435,18 +442,18 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas else: export.set() =} + } - } mode Eat { - + mode Eat { reaction(reset) -> history(export) {= #print("food time") self.character_instance.ai_eat(pacman.walls, self._ghosts, self._block_list, self._eat_moves) self._eat_moves = self.character_instance.get_num_moves() export.set() =} - - } mode Scared { + } + mode Scared { reaction(reset) -> reset(Avoid), reset(Chase) {= if self._frenzy: #print("pac not scared") @@ -455,51 +462,49 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas #print("pac is scared") Avoid.set() =} + } - } mode Avoid { - + mode Avoid { reaction(reset) -> history(export) {= print("avoid time") self.character_instance.ai_avoid(self._layout, self._ghosts, 7) #self._avoid_moves = self.character_instance.get_num_moves() export.set() =} + } - } mode Chase { - + mode Chase { reaction(reset) -> history(export) {= print("chase time") self.character_instance.ai_chase(self._layout, self._ghosts, 7) export.set() =} - } } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart input frenzy - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - state _frenzy(False) - - #reaction(startup) {= - # self.image = "images/pacman.png" + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + state _frenzy = False + + # reaction(startup) {= + # self.image = "images/pacman.png" #=} - reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -531,7 +536,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -543,62 +548,146 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - + reaction(frenzy) {= self._frenzy = frenzy.value =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - + logical action end_frenzy - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - state _frenzy(False) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite) {= - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + state _frenzy = False + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite) {= + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) > 0: + self._frenzy = True + print(self._frenzy) + frenzy.set(self._frenzy) + delay = randint(3, 7) + end_frenzy.schedule(SEC(delay)) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list and not self._frenzy: + game_over.set("Lost!") + elif monsta_hit_list and self._frenzy: + for monsta in monsta_hit_list: + monsta.moveoffgrid() + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -608,126 +697,37 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite) -> score, game_over, frenzy, end_frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) > 0: - self._frenzy = True - print(self._frenzy) - frenzy.set(self._frenzy) - delay = randint(3, 7) - end_frenzy.schedule(SEC(delay)) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list and not self._frenzy: - game_over.set("Lost!") - elif monsta_hit_list and self._frenzy: - for monsta in monsta_hit_list: - monsta.moveoffgrid() - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._frenzy = False - self._score = 0 - =} - - reaction(end_frenzy) -> frenzy {= self._frenzy = False - print(self._frenzy, " and times up") - frenzy.set(self._frenzy) - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} + self._score = 0 + =} - # } + reaction(end_frenzy) -> frenzy {= + self._frenzy = False + print(self._frenzy, " and times up") + frenzy.set(self._frenzy) + =} + + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -735,63 +735,53 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - #width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + + # width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png" # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) - # image = {= ghost_specs[bank_index]["image"] =} + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) + # image = {= ghost_specs[bank_index]["image"] =} ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + (ghosts.sprite)+ -> controller.ghost_sprites, player.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - + (controller.frenzy)+ -> player.frenzy, ghosts.frenzy - + controller.block_list -> player.block_list - } - \ No newline at end of file diff --git a/experiments/Python/src/Pac-Man/PacManWRestart.lf b/experiments/Python/src/Pac-Man/PacManWRestart.lf index 51c66663..8a98a45f 100644 --- a/experiments/Python/src/Pac-Man/PacManWRestart.lf +++ b/experiments/Python/src/Pac-Man/PacManWRestart.lf @@ -1,36 +1,38 @@ /** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images"] -}; +} preamble {= import os @@ -40,7 +42,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -49,19 +51,21 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p output tick output[5] icon - + state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text + timer pygame_tick(0, 100 msec) # 10 FPS + reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -69,22 +73,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -100,7 +102,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -120,7 +122,7 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - + reaction(game_over) {= #Grey background w = pacman.pygame.Surface((400,200)) # the size of your rect @@ -143,9 +145,13 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon output sprite @@ -169,13 +175,13 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact self._wall_list = wall_list.value self._gate_list = gate_list.value =} - } ## Player # Should be replacable with an AI reactor Player extends BaseCharacter { timer pygame_event(0, 100 msec) + reaction(pygame_event) -> sprite {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: @@ -200,7 +206,7 @@ reactor Player extends BaseCharacter { self.character_instance.changespeed(0, 30) if event.key == pacman.pygame.K_DOWN: self.character_instance.changespeed(0, -30) - + self.character_instance.update( self._wall_list, self._gate_list @@ -211,10 +217,10 @@ reactor Player extends BaseCharacter { ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=})) extends BaseCharacter { - input tick - state turn(0) - state steps(0) +reactor Ghost(directions = {= () =}) extends BaseCharacter { + input tick + state turn = 0 + state steps = 0 reaction(tick) -> sprite {= returned = self.character_instance.changespeed( @@ -239,29 +245,28 @@ reactor Ghost (directions({=()=})) extends BaseCharacter { ) sprite.set(self.character_instance) =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over input[number_of_ghosts] ghost_sprites input pacman_sprite - input tick # The game tick + input tick # The game tick state _wall_list state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + reaction(startup) -> wall_list, gate {= _all_sprites_list = pacman.pygame.sprite.RenderPlain() self._wall_list = pacman.setupRoomOne(_all_sprites_list) @@ -269,22 +274,21 @@ reactor GameController(number_of_ghosts(4)) { wall_list.set(self._wall_list) gate.set(self._gate) - =} - + reaction(pacman_sprite) {= self._pacman_collide.empty() self._pacman_collide.add(pacman_sprite.value) self._pacman_sprite = pacman_sprite.value =} - + reaction(startup) -> block_list {= # Draw the grid for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): continue - + block = pacman.Block(pacman.yellow, 4, 4) # Set a random location for the block @@ -293,12 +297,12 @@ reactor GameController(number_of_ghosts(4)) { b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) block_list.set(block) # Send it to be drawn @@ -308,7 +312,7 @@ reactor GameController(number_of_ghosts(4)) { reaction(pacman_sprite) -> score, game_over {= blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - + # Check the list of collisions. if len(blocks_hit_list) > 0: self._score +=len(blocks_hit_list) @@ -317,7 +321,7 @@ reactor GameController(number_of_ghosts(4)) { game_over.set("Won!") request_stop() - + score.set(self._score) =} @@ -327,21 +331,19 @@ reactor GameController(number_of_ghosts(4)) { for ghost in ghost_sprites: if ghost.is_present: monsta_list.add(ghost.value) - + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) if monsta_hit_list: game_over.set("Lost!") request_stop() - =} # Send the updated blocks - reaction(tick) -> block_list {= + reaction(tick) -> block_list {= block_list.set(self._block_list) =} - - + reaction(shutdown) {= pacman.pygame.quit() =} @@ -352,89 +354,59 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + # Ghosts pinky = new Ghost( - width = {=pacman.w=}, - height = {=pacman.m_h=}, - image = "images/Pinky.png", - directions = {=pacman.Pinky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.w =}, + height = {= pacman.m_h =}, + image="images/Pinky.png", + directions = {= pacman.Pinky_directions =}, + character_class = {= pacman.Ghost =}) blinky = new Ghost( - width = {=pacman.w=}, - height = {=pacman.b_h=}, - image = "images/Blinky.png", - directions = {=pacman.Blinky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.w =}, + height = {= pacman.b_h =}, + image="images/Blinky.png", + directions = {= pacman.Blinky_directions =}, + character_class = {= pacman.Ghost =}) inky = new Ghost( - width = {=pacman.i_w=}, - height = {=pacman.m_h=}, - image = "images/Inky.png", - directions = {=pacman.Inky_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.i_w =}, + height = {= pacman.m_h =}, + image="images/Inky.png", + directions = {= pacman.Inky_directions =}, + character_class = {= pacman.Ghost =}) clyde = new Ghost( - width = {=pacman.c_w=}, - height = {=pacman.m_h=}, - image = "images/Clyde.png", - directions = {=pacman.Clyde_directions=}, - character_class = ({=pacman.Ghost=}) - ) + width = {= pacman.c_w =}, + height = {= pacman.m_h =}, + image="images/Clyde.png", + directions = {= pacman.Clyde_directions =}, + character_class = {= pacman.Ghost =}) ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls - (controller.wall_list)+ -> - pinky.wall_list, - blinky.wall_list, - inky.wall_list, - clyde.wall_list + (controller.wall_list)+ -> pinky.wall_list, blinky.wall_list, inky.wall_list, clyde.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - pinky.sprite, - blinky.sprite, - inky.sprite, - clyde.sprite -> - display.moving_sprites + controller.block_list, player.sprite, pinky.sprite, blinky.sprite, inky.sprite, clyde.sprite + -> display.moving_sprites controller.game_over -> display.game_over - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - (display.tick)+ -> - controller.tick, - pinky.tick, - blinky.tick, - inky.tick, - clyde.tick + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, pinky.tick, blinky.tick, inky.tick, clyde.tick player.sprite -> controller.pacman_sprite - - pinky.sprite, - blinky.sprite, - inky.sprite, - clyde.sprite-> controller.ghost_sprites - - controller.score -> display.score - pinky.icon_name, - blinky.icon_name, - inky.icon_name, - clyde.icon_name, - player.icon_name -> display.icon_name + pinky.sprite, blinky.sprite, inky.sprite, clyde.sprite -> controller.ghost_sprites + + controller.score -> display.score - display.icon -> - pinky.icon, - blinky.icon, - inky.icon, - clyde.icon, player.icon + pinky.icon_name, blinky.icon_name, inky.icon_name, clyde.icon_name, player.icon_name + -> display.icon_name + display.icon -> pinky.icon, blinky.icon, inky.icon, clyde.icon, player.icon } diff --git a/experiments/Python/src/Pac-Man/PacManWithBank.lf b/experiments/Python/src/Pac-Man/PacManWithBank.lf index b5da31c9..9571870c 100644 --- a/experiments/Python/src/Pac-Man/PacManWithBank.lf +++ b/experiments/Python/src/Pac-Man/PacManWithBank.lf @@ -1,36 +1,38 @@ /** * A simple Pacman game. + * * Source: https://github.com/hbokmann/Pacman + * * LICENSE: N/A * * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * + * -1- lfc src/PacMan.lf + * -2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images"] -}; +} preamble {= import os @@ -74,30 +76,31 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over input score input[5] icon_name - #input controllerpause - output tick + output tick # input controllerpause output[5] icon - + state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) + state _active = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -105,22 +108,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -128,16 +129,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) =} - + reaction(score) {= #if self._active: self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) @@ -153,52 +154,55 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - + reaction(game_over) {= #self._active = False =} - - reaction(game_over) {= - #Grey background - print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=font.render("To play again, press ENTER.", True, white) - screen.blit(text2, [135, 303]) - text3=font.render("To quit, press ESCAPE.", True, white) - screen.blit(text3, [165, 333]) - pacman.pygame.display.flip() + reaction(game_over) {= + #Grey background + print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=font.render("To play again, press ENTER.", True, white) + screen.blit(text2, [135, 303]) + text3=font.render("To quit, press ESCAPE.", True, white) + screen.blit(text3, [165, 333]) + + pacman.pygame.display.flip() =} } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -210,34 +214,30 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } ## Player # Should be replacable with an AI reactor Player extends BaseCharacter { timer pygame_event(0, 100 msec) - state _active(True) - - #input game_over - - output playerpause - - /*reaction(game_over) -> playerpause {= - self._active(False) - self._pause = True - playerpause.set(self._pause) - =}*/ - + state _active = True + + output playerpause # input game_over + + /** + * reaction(game_over) -> playerpause {= self._active(False) self._pause = True + * playerpause.set(self._pause) + * =} + */ reaction(pygame_event) -> sprite, playerpause {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: @@ -250,7 +250,7 @@ reactor Player extends BaseCharacter { else: self._pause = False print(self._pause) - elif self._pause is False and self._active: + elif self._pause is False and self._active: if event.key == pacman.pygame.K_LEFT: self.character_instance.changespeed(-30, 0) if event.key == pacman.pygame.K_RIGHT: @@ -259,9 +259,9 @@ reactor Player extends BaseCharacter { self.character_instance.changespeed(0, -30) if event.key == pacman.pygame.K_DOWN: self.character_instance.changespeed(0, 30) - - if event.type == pacman.pygame.KEYUP and self._pause is False and self._active: + + if event.type == pacman.pygame.KEYUP and self._pause is False and self._active: if event.key == pacman.pygame.K_LEFT: self.character_instance.changespeed(30, 0) if event.key == pacman.pygame.K_RIGHT: @@ -282,18 +282,18 @@ reactor Player extends BaseCharacter { ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=})) extends BaseCharacter { - input tick - input playerpause # pause from player - - state turn(0) - state steps(0) - +reactor Ghost(directions = {= () =}) extends BaseCharacter { + input tick + input playerpause # pause from player + + state turn = 0 + state steps = 0 + reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -321,31 +321,29 @@ reactor Ghost (directions({=()=})) extends BaseCharacter { ) sprite.set(self.character_instance) =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick - + input tick # The game tick + state _wall_list state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - # state _controllerpause({=False=}) #not necessary - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + + # state _controllerpause({=False=}) #not necessary reaction(startup) -> wall_list, gate {= _all_sprites_list = pacman.pygame.sprite.RenderPlain() self._wall_list = pacman.setupRoomOne(_all_sprites_list) @@ -353,22 +351,21 @@ reactor GameController(number_of_ghosts(4)) { wall_list.set(self._wall_list) gate.set(self._gate) - =} - + reaction(pacman_sprite) {= self._pacman_collide.empty() self._pacman_collide.add(pacman_sprite.value) self._pacman_sprite = pacman_sprite.value =} - + reaction(startup) -> block_list {= # Draw the grid for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): continue - + block = pacman.Block(pacman.yellow, 4, 4) # Set a random location for the block @@ -377,12 +374,12 @@ reactor GameController(number_of_ghosts(4)) { b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) block_list.set(block) # Send it to be drawn @@ -392,7 +389,7 @@ reactor GameController(number_of_ghosts(4)) { reaction(pacman_sprite) -> score, game_over {= blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - + # Check the list of collisions. if len(blocks_hit_list) > 0: self._score +=len(blocks_hit_list) @@ -405,8 +402,8 @@ reactor GameController(number_of_ghosts(4)) { #pause isntead of stop game #controllerpause.set(True) #request_stop() #remove once above finished - - + + score.set(self._score) =} @@ -416,7 +413,7 @@ reactor GameController(number_of_ghosts(4)) { for ghost in ghost_sprites: if ghost.is_present: monsta_list.add(ghost.value) - + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) if monsta_hit_list: @@ -428,15 +425,13 @@ reactor GameController(number_of_ghosts(4)) { #Pause the game instead of stopping it #controllerpause.set(True) #request_stop() #remove once above finished - =} # Send the updated blocks - reaction(tick) -> block_list {= + reaction(tick) -> block_list {= block_list.set(self._block_list) =} - - + reaction(shutdown) {= pacman.pygame.quit() =} @@ -447,54 +442,45 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - image = {= ghost_specs[bank_index]["image"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - character_class = ({=pacman.Ghost=}) - ) + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + image = {= ghost_specs[bank_index]["image"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + character_class = {= pacman.Ghost =}) ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + ghosts.sprite -> controller.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem controller.game_over -> display.game_over - } diff --git a/experiments/Python/src/Pac-Man/oldWBank.lf b/experiments/Python/src/Pac-Man/oldWBank.lf index 13facf8e..378f3c02 100644 --- a/experiments/Python/src/Pac-Man/oldWBank.lf +++ b/experiments/Python/src/Pac-Man/oldWBank.lf @@ -1,36 +1,31 @@ - /** - * A simple Pacman game. - * Source: https://github.com/hbokmann/Pacman - * LICENSE: N/A +/** + * A simple Pacman game. Source: https://github.com/hbokmann/Pacman LICENSE: N/A * - * To run: - * 1- lfc src/PacMan.lf - * 2- Follow the instructions on the terminal. + * To run: 1- lfc src/PacMan.lf 2- Follow the instructions on the terminal. * * TODOs - * 1- Add more comments. - * 2- Show a win/lose screen instead of exiting. - * 3- Add the ability to restart the game after win/lose. - * 4- Make the game logic more efficient if possible. - * 5- Add personalities for each ghost instead of following pre-determined - * directions. - * 6- Add modes for ghosts (exploring, chasing, running away). - * 7- Replace the player with an AI. - * 8- Enable federated execution if possible. - * 9- Explore: - * - What to do in the case of communication failure? - * - What are other possible fault scenarios? - * - What should the AI and the ghosts see? Should they be able to see all the - * walls or just walls close to them? - * - Add an external observer that is responsible for veryfing safety - * properties. - * - Explore consistency vs. availability tradeoffs in the game design. - * See https://arxiv.org/abs/2109.07771 . * -**/ + * -1- Add more comments. + * -2- Show a win/lose screen instead of exiting. + * -3- Add the ability to restart the game after win/lose. + * -4- Make the game logic more efficient if possible. + * -5- Add personalities for each ghost instead of following pre-determined directions. + * -6- Add modes for ghosts (exploring, chasing, running away). + * -7- Replace the player with an AI. + * -8- Enable federated execution if possible. + * -9- Explore: + * --> What to do in the case of communication failure? + * --> What are other possible fault scenarios? + * --> What should the AI and the ghosts see? Should they be able to see all the walls or just walls + * close to them? + * --> Add an external observer that is responsible for veryfing safety properties. + * --> Explore consistency vs. availability tradeoffs in the game design. + * + * See https://arxiv.org/abs/2109.07771 + */ target Python { files: ["include/hbpacman.py", "include/images"] -}; +} preamble {= import os @@ -75,7 +70,7 @@ preamble {= =} #### View -reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/pacman.png")) { +reactor Display(num_moving_sprites=0, num_static_sprites=0, nav_icon="images/pacman.png") { input[num_moving_sprites] moving_sprites input[num_static_sprites] static_sprites input game_over @@ -83,28 +78,30 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p input[5] icon_name input playerpause input restart - #logical action announcement - #logical action announcementval - #input controllerpause + # logical action announcement + # logical action announcementval + # input controllerpause output tick output[5] icon - - state _game_over(False) + + state _game_over = False state _screen state _font state _clock - state _static_sprites({=pacman.pygame.sprite.RenderPlain()=}) + state _static_sprites = {= pacman.pygame.sprite.RenderPlain() =} state _top_corner_text - state _active(True) - state _announcement(True) + state _active = True + state _announcement = True + + timer pygame_tick(0, 100 msec) # 10 FPS reaction(startup) {= dirname = os.path.dirname(__file__) pacman_icon=pacman.pygame.image.load(os.path.join(dirname, self.nav_icon)) pacman.pygame.display.set_icon(pacman_icon) - self._clock = pacman.pygame.time.Clock() + self._clock = pacman.pygame.time.Clock() # Create an 606x606 sized screen self._screen = pacman.pygame.display.set_mode([606, 606]) # Set the title of the window @@ -112,22 +109,20 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p # Create a surface we can draw on background = pacman.pygame.Surface(self._screen.get_size()) # Used for converting color maps and such - background = background.convert() + background = background.convert() # Fill the screen with a black background background.fill(pacman.black) pacman.pygame.font.init() self._font = pacman.pygame.font.Font("freesansbold.ttf", 24) self._screen.fill(pacman.black) - =} - reaction (icon_name) -> icon {= + reaction(icon_name) -> icon {= for (idx, name) in enumerate(icon_name): if name.is_present: icon[idx].set(pacman.pygame.image.load(name.value).convert()) =} - timer pygame_tick(0, 100 msec) # 10 FPS reaction(pygame_tick) -> tick {= pacman.pygame.display.flip() self._clock.tick() @@ -138,16 +133,16 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p =} reaction(static_sprites) {= - # if self._active: - for sprite in static_sprites: - if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): - self._static_sprites.add(sprite.value.sprites()) - elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): - self._static_sprites.add(sprite.value) - - self._static_sprites.draw(self._screen) + # if self._active: + for sprite in static_sprites: + if sprite.is_present and isinstance(sprite.value, pacman.pygame.sprite.Group): + self._static_sprites.add(sprite.value.sprites()) + elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): + self._static_sprites.add(sprite.value) + + self._static_sprites.draw(self._screen) =} - + reaction(score) {= self._top_corner_text=self._font.render("Score: "+str(score.value), True, pacman.red) self._screen.blit(self._top_corner_text, [10, 10]) @@ -161,36 +156,35 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p sprite.value.draw(self._screen) elif isinstance(sprite.value, pacman.pygame.sprite.Sprite): sprite_list.add(sprite.value) - + sprite_list.draw(self._screen) self._static_sprites.draw(self._screen) self._screen.blit(self._top_corner_text, [10, 10]) =} - - #reaction(game_over) {= - #self._active = False - # =} -// reaction(playerpause) {= -// if playerpause.is_present and self._game_over == False: -// w = pacman.pygame.Surface((400,200)) # the size of your rect -// w.set_alpha(10) # alpha level -// w.fill((128,128,128)) # this fills the entire surface -// self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates -// -// text2=self._font.render("The game is paused.", True, pacman.white) -// self._screen.blit(text2, [135, 303]) -// text3=self._font.render("To continue, hit SPACE.", True, pacman.white) -// self._screen.blit(text3, [165, 333]) -// pacman.pygame.display.flip() -// =} + # reaction(game_over) {= + # self._active = False + # =} + # reaction(playerpause) {= + # if playerpause.is_present and self._game_over == False: + # w = pacman.pygame.Surface((400,200)) # the size of your rect + # w.set_alpha(10) # alpha level + # w.fill((128,128,128)) # this fills the entire surface + # self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + # + # text2=self._font.render("The game is paused.", True, pacman.white) + # self._screen.blit(text2, [135, 303]) + # text3=self._font.render("To continue, hit SPACE.", True, pacman.white) + # self._screen.blit(text3, [165, 333]) + # pacman.pygame.display.flip() + # =} reaction(playerpause) {= if playerpause.is_present and playerpause.value == True and self._game_over == False: w = pacman.pygame.Surface((400,200)) # the size of your rect w.set_alpha(10) # alpha level w.fill((128,128,128)) # this fills the entire surface self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - + text2=self._font.render("The game is paused.", True, pacman.white) self._screen.blit(text2, [135, 303]) text3=self._font.render("To continue, hit SPACE.", True, pacman.white) @@ -198,57 +192,59 @@ reactor Display(num_moving_sprites(0), num_static_sprites(0), nav_icon("images/p pacman.pygame.display.flip() =} - reaction(pygame_tick, game_over) {= + reaction(pygame_tick, game_over) {= #Grey background - if game_over.is_present: - self._game_over = True - pyautogui.keyDown(' ') - #print("The display is ", self._active) - w = pacman.pygame.Surface((400,200)) # the size of your rect - w.set_alpha(10) # alpha level - w.fill((128,128,128)) # this fills the entire surface - self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates - - #Won or lost - text1=self._font.render(game_over.value, True, pacman.white) - self._screen.blit(text1, [235, 233]) - - text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) - self._screen.blit(text2, [135, 303]) - text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) - self._screen.blit(text3, [165, 333]) - pyautogui.keyUp(' ') - pacman.pygame.display.flip() - - #for event in pacman.pygame.event.get(): - # if event.type == pacman.pygame.KEYDOWN: - # print(1) + if game_over.is_present: + self._game_over = True + pyautogui.keyDown(' ') + #print("The display is ", self._active) + w = pacman.pygame.Surface((400,200)) # the size of your rect + w.set_alpha(10) # alpha level + w.fill((128,128,128)) # this fills the entire surface + self._screen.blit(w, (100,200)) # (0,0) are the top-left coordinates + + #Won or lost + text1=self._font.render(game_over.value, True, pacman.white) + self._screen.blit(text1, [235, 233]) + + text2=self._font.render("To play again, press ENTER or R.", True, pacman.white) + self._screen.blit(text2, [135, 303]) + text3=self._font.render("To quit, press ESCAPE.", True, pacman.white) + self._screen.blit(text3, [165, 333]) + pyautogui.keyUp(' ') + pacman.pygame.display.flip() + + #for event in pacman.pygame.event.get(): + # if event.type == pacman.pygame.KEYDOWN: + # print(1) =} + reaction(restart) {= #if restart.value: # self._active = True # pyautogui.keyUp(' ') self._game_over = False =} - - } #### Model ## Base of every character -reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list +reactor BaseCharacter( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { + input wall_list # Receive updated wall list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) + state _pause = {= False =} reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -260,52 +256,51 @@ reactor BaseCharacter(width(0), height(0), image("images/Trollman.png"), charact sprite.set(self.character_instance) =} + # reaction to update pause from controller + /** + * reaction(controllerpause) {= self._pause = controllerpause + * =}* + */ reaction(wall_list, gate_list) {= self._wall_list = wall_list.value self._gate_list = gate_list.value =} - - # reaction to update pause from controller - /**reaction(controllerpause) {= - self._pause = controllerpause - =}**/ - } ## Player # Should be replacable with an AI -reactor Player(width(0), height(0), image("images/Trollman.png"), character_class({=pacman.Player=})) { +reactor Player( + width=0, + height=0, + image="images/Trollman.png", + character_class = {= pacman.Player =}) { timer pygame_event(0, 100 msec) - state _active(True) - + state _active = True + input game_over -// logical action ending - output playerpause + output playerpause # logical action ending output restart - - state _speed(30) - state _boosted(False) - - -// -// reaction(ending) {= -// print(1) -// =} - - input wall_list # Receive updated wall list - input gate_list # Receive updated gate list + + state _speed = 30 + state _boosted = False + + # + # reaction(ending) {= + # print(1) + # =} + # Receive updated wall list + input wall_list + input gate_list # Receive updated gate list input icon - #input controllerpause # from controller to pause at end - output sprite + output sprite # input controllerpause # from controller to pause at end output icon_name state character_instance state _wall_list state _gate_list - state _pause({=False=}) - state _resetting(False) - + state _pause = {= False =} + state _resetting = False reaction(startup) -> icon_name {= dirname = os.path.dirname(__file__) @@ -321,13 +316,13 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self._wall_list = wall_list.value self._gate_list = gate_list.value =} - + reaction(pygame_event) -> sprite, playerpause, restart {= keyboard_events = pacman.pygame.event.get() for event in keyboard_events: if event.type == pacman.pygame.QUIT: request_stop() - + if event.type == pacman.pygame.KEYDOWN: if event.key == pacman.pygame.K_ESCAPE: request_stop() @@ -343,7 +338,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas elif event.key == pacman.pygame.K_b and self._boosted == True: self._speed -= 15 self._boosted = False - + if event.key == pacman.pygame.K_SPACE and self._active: if self._pause is False: self._pause = True @@ -354,7 +349,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas #self.character_instance.resetpos() #restart.set(True) print("do not remove comments") - elif self._pause is False: + elif self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed * -1, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -363,8 +358,8 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_DOWN or event.key == pacman.pygame.K_s: self.character_instance.changespeed(0, self._speed) - - if event.type == pacman.pygame.KEYUP and self._pause is False: + + if event.type == pacman.pygame.KEYUP and self._pause is False: if event.key == pacman.pygame.K_LEFT or event.key == pacman.pygame.K_a: self.character_instance.changespeed(self._speed, 0) if event.key == pacman.pygame.K_RIGHT or event.key == pacman.pygame.K_d: @@ -375,7 +370,7 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas self.character_instance.changespeed(0, self._speed * -1) if event.key == pacman.pygame.K_b: self._speed -= 10 - + if self._pause is False: self.character_instance.update( self._wall_list, @@ -384,34 +379,33 @@ reactor Player(width(0), height(0), image("images/Trollman.png"), character_clas sprite.set(self.character_instance) playerpause.set(self._pause) =} - + reaction(game_over) {= - self._active = False - self._pause = True - self.character_instance.speedzero() -# ending.schedule(MSEC(500)) + self._active = False + self._pause = True + self.character_instance.speedzero() + # ending.schedule(MSEC(500)) =} - } ## Ghosts # FIXME: Different Ghosts should have different personalities -reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { - input tick - input playerpause # pause from player +reactor Ghost(directions = {= () =}, name="Stinky") extends BaseCharacter { + input tick + input playerpause # pause from player input game_over input restart - - state turn(0) - state steps(0) - state _active(True) - state _resetting(False) - + + state turn = 0 + state steps = 0 + state _active = True + state _resetting = False + reaction(playerpause) {= - # if controllerpause.is_present: - # self._pause = controllerpause.value - if playerpause.is_present: - self._pause = playerpause.value + # if controllerpause.is_present: + # self._pause = controllerpause.value + if playerpause.is_present: + self._pause = playerpause.value =} reaction(tick) -> sprite {= @@ -443,7 +437,7 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { # self._resetting = False sprite.set(self.character_instance) =} - + reaction(game_over) {= self._active = False =} @@ -455,55 +449,141 @@ reactor Ghost (directions({=()=}), name("Stinky")) extends BaseCharacter { self.steps = 0 self.character_instance.resetpos(self.name) =} - } #### Controller -reactor GameController(number_of_ghosts(4)) { - output wall_list # List of walls on the map +reactor GameController(number_of_ghosts=4) { + output wall_list # List of walls on the map output gate - output block_list # List of yummy dots for Pac-Man - output score # The game score + output block_list # List of yummy dots for Pac-Man + output score # The game score output game_over output frenzy - #output controllerpause - input[number_of_ghosts] ghost_sprites + input[number_of_ghosts] ghost_sprites # output controllerpause input pacman_sprite - input tick # The game tick + input tick # The game tick input restart - - state _wall_list - state _gate - state _block_list({=pacman.pygame.sprite.RenderPlain()=}) - state _score_to_win(0) - state _score(0) + + state _wall_list + state _gate + state _block_list = {= pacman.pygame.sprite.RenderPlain() =} + state _score_to_win = 0 + state _score = 0 state _pacman_sprite - state _pacman_collide({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_list({=pacman.pygame.sprite.RenderPlain()=}) - state _energizer_indices({=[0, 0]=}) - # state _controllerpause({=False=}) #not necessary - - # initial mode One { - reaction(startup) -> wall_list, gate {= - _all_sprites_list = pacman.pygame.sprite.RenderPlain() - self._wall_list = pacman.setupRoomOne(_all_sprites_list) - self._gate = pacman.setupGate(_all_sprites_list) - - wall_list.set(self._wall_list) - gate.set(self._gate) - - =} - - reaction(pacman_sprite) {= - self._pacman_collide.empty() - self._pacman_collide.add(pacman_sprite.value) - self._pacman_sprite = pacman_sprite.value - =} - - reaction(startup) -> block_list {= - # Draw the grid - + state _pacman_collide = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_list = {= pacman.pygame.sprite.RenderPlain() =} + state _energizer_indices = {= [0, 0] =} + + # state _controllerpause({=False=}) #not necessary + # initial mode One { + reaction(startup) -> wall_list, gate {= + _all_sprites_list = pacman.pygame.sprite.RenderPlain() + self._wall_list = pacman.setupRoomOne(_all_sprites_list) + self._gate = pacman.setupGate(_all_sprites_list) + + wall_list.set(self._wall_list) + gate.set(self._gate) + =} + + reaction(pacman_sprite) {= + self._pacman_collide.empty() + self._pacman_collide.add(pacman_sprite.value) + self._pacman_sprite = pacman_sprite.value + =} + + reaction(startup) -> block_list {= + # Draw the grid + + for row in range(19): + for column in range(19): + if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): + continue + if randint(0, 361) > 358: + block = pacman.Block(pacman.red, 10, 10) + self._energizer_list.add(block) + else: + block = pacman.Block(pacman.yellow, 4, 4) + + # Set a random location for the block + block.rect.x = (30*column+6)+26 + block.rect.y = (30*row+6)+26 + + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) + p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) + + if b_collide: + continue + if p_collide: + continue + + # Add the block to the list of objects + self._block_list.add(block) + block_list.set(block) # Send it to be drawn + # print("Finished drawing blocks") + + self._score_to_win = len(self._block_list) + print(self._energizer_list) + print(self._score_to_win) + =} + + reaction(pacman_sprite) -> score, game_over, frenzy {= + blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) + energizer_hit_list = pacman.pygame.sprite.RenderPlain() + #FIXME: can also make this more efficient + for block in blocks_hit_list: + if block.rect.width == 10: + energizer_hit_list.add(block) + print(len(energizer_hit_list)) + + # Check the list of collisions. + if len(blocks_hit_list) > 0: + self._score +=len(blocks_hit_list) + + if self._score == self._score_to_win: + game_over.set("Won!") + #pyautogui.keyDown(' ') + #print("Won") + #self._controllerpause = True + #pause isntead of stop game + #controllerpause.set(True) + #request_stop() #remove once above finished + elif len(energizer_hit_list) >= 1: + print("frenzy time") + frenzy.set(True) + + score.set(self._score) + =} + + reaction(ghost_sprites) -> game_over {= + # FIXME: Make this more efficient. + monsta_list = pacman.pygame.sprite.RenderPlain() + for ghost in ghost_sprites: + if ghost.is_present: + monsta_list.add(ghost.value) + + monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) + + if monsta_hit_list: + game_over.set("Lost!") + #print("Lost") + #pyautogui.keyDown(' ') + #print("Lost") + #self._controllerpause = True + #Pause the game instead of stopping it + #controllerpause.set(True) + #request_stop() #remove once above finished + =} + + # Send the updated blocks + reaction(tick) -> block_list {= + block_list.set(self._block_list) + =} + + reaction(restart) {= + if restart.value: + self._block_list = pacman.pygame.sprite.RenderPlain() + self._energizer_list = pacman.pygame.sprite.RenderPlain() for row in range(19): for column in range(19): if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): @@ -513,121 +593,30 @@ reactor GameController(number_of_ghosts(4)) { self._energizer_list.add(block) else: block = pacman.Block(pacman.yellow, 4, 4) - + # Set a random location for the block block.rect.x = (30*column+6)+26 block.rect.y = (30*row+6)+26 - + b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - + if b_collide: continue if p_collide: continue - + # Add the block to the list of objects self._block_list.add(block) - block_list.set(block) # Send it to be drawn - # print("Finished drawing blocks") - self._score_to_win = len(self._block_list) print(self._energizer_list) - print(self._score_to_win) - =} - - reaction(pacman_sprite) -> score, game_over, frenzy {= - blocks_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, self._block_list, True) - energizer_hit_list = pacman.pygame.sprite.RenderPlain() - #FIXME: can also make this more efficient - for block in blocks_hit_list: - if block.rect.width == 10: - energizer_hit_list.add(block) - print(len(energizer_hit_list)) - - # Check the list of collisions. - if len(blocks_hit_list) > 0: - self._score +=len(blocks_hit_list) - - if self._score == self._score_to_win: - game_over.set("Won!") - #pyautogui.keyDown(' ') - #print("Won") - #self._controllerpause = True - #pause isntead of stop game - #controllerpause.set(True) - #request_stop() #remove once above finished - elif len(energizer_hit_list) >= 1: - print("frenzy time") - frenzy.set(True) - - score.set(self._score) - =} - - reaction(ghost_sprites) -> game_over {= - # FIXME: Make this more efficient. - monsta_list = pacman.pygame.sprite.RenderPlain() - for ghost in ghost_sprites: - if ghost.is_present: - monsta_list.add(ghost.value) - - monsta_hit_list = pacman.pygame.sprite.spritecollide(self._pacman_sprite, monsta_list, False) - - if monsta_hit_list: - game_over.set("Lost!") - #print("Lost") - #pyautogui.keyDown(' ') - #print("Lost") - #self._controllerpause = True - #Pause the game instead of stopping it - #controllerpause.set(True) - #request_stop() #remove once above finished - - =} - - # Send the updated blocks - reaction(tick) -> block_list {= - block_list.set(self._block_list) - =} - - reaction(restart) {= - if restart.value: - self._block_list = pacman.pygame.sprite.RenderPlain() - self._energizer_list = pacman.pygame.sprite.RenderPlain() - for row in range(19): - for column in range(19): - if (row == 7 or row == 8) and (column == 8 or column == 9 or column == 10): - continue - if randint(0, 361) > 358: - block = pacman.Block(pacman.red, 10, 10) - self._energizer_list.add(block) - else: - block = pacman.Block(pacman.yellow, 4, 4) - - # Set a random location for the block - block.rect.x = (30*column+6)+26 - block.rect.y = (30*row+6)+26 - - b_collide = pacman.pygame.sprite.spritecollide(block, self._wall_list, False) - p_collide = pacman.pygame.sprite.spritecollide(block, self._pacman_collide, False) - - if b_collide: - continue - if p_collide: - continue - - # Add the block to the list of objects - self._block_list.add(block) - self._score_to_win = len(self._block_list) - print(self._energizer_list) - self._score = 0 - =} - - reaction(shutdown) {= - pacman.pygame.quit() - =} - - # } + self._score = 0 + =} + + # } + reaction(shutdown) {= + pacman.pygame.quit() + =} } main reactor { @@ -635,59 +624,48 @@ main reactor { controller = new GameController() ### Model(s) - player = new Player(width = {=pacman.w=}, height = {=pacman.p_h=}, image = "images/pacman.png") - + player = new Player(width = {= pacman.w =}, height = {= pacman.p_h =}, image="images/pacman.png") + # Ghosts ghosts = new[4] Ghost( - width = {= ghost_specs[bank_index]["width"] =}, - height = {= ghost_specs[bank_index]["height"] =}, - image = {= ghost_specs[bank_index]["image"] =}, - directions = {= ghost_specs[bank_index]["directions"] =}, - name = {= ghost_specs[bank_index]["name"] =}, - character_class = ({=pacman.Ghost=}) - ) + width = {= ghost_specs[bank_index]["width"] =}, + height = {= ghost_specs[bank_index]["height"] =}, + image = {= ghost_specs[bank_index]["image"] =}, + directions = {= ghost_specs[bank_index]["directions"] =}, + name = {= ghost_specs[bank_index]["name"] =}, + character_class = {= pacman.Ghost =}) ### View - display = new Display( num_moving_sprites = 6, num_static_sprites = 2 ) + display = new Display(num_moving_sprites=6, num_static_sprites=2) # Send the list of walls to the ghosts so that they can avoid running into walls (controller.wall_list)+ -> ghosts.wall_list # Send the sprites to the display to be drawn - - controller.block_list, - player.sprite, - ghosts.sprite -> - display.moving_sprites - - (controller.wall_list, controller.gate)+ -> - player.wall_list, player.gate_list, display.static_sprites - - (display.tick)+ -> - controller.tick, - ghosts.tick - - #Send pause player to game controller and ghosts + controller.block_list, player.sprite, ghosts.sprite -> display.moving_sprites + + (controller.wall_list, controller.gate)+ + -> player.wall_list, player.gate_list, display.static_sprites + + (display.tick)+ -> controller.tick, ghosts.tick + + # Send pause player to game controller and ghosts (player.playerpause)+ -> ghosts.playerpause, display.playerpause - - #Send pause controller to player and ghosts - #controller.controllerpause -> player.controllerpause - + + # Send pause controller to player and ghosts + # controller.controllerpause -> player.controllerpause player.sprite -> controller.pacman_sprite - + ghosts.sprite -> controller.ghost_sprites - + controller.score -> display.score ghosts.icon_name, player.icon_name -> display.icon_name display.icon -> ghosts.icon, player.icon - - #sending game_over to player causes problem + + # sending game_over to player causes problem (controller.game_over)+ -> display.game_over, player.game_over, ghosts.game_over - - + (player.restart)+ -> controller.restart, display.restart, ghosts.restart - } - \ No newline at end of file diff --git a/experiments/Python/src/Piano/Piano.lf b/experiments/Python/src/Piano/Piano.lf index b4cd8d3d..3b8c26d2 100644 --- a/experiments/Python/src/Piano/Piano.lf +++ b/experiments/Python/src/Piano/Piano.lf @@ -9,7 +9,7 @@ */ target Python { files: [gui.py, keys.png, soundfont.sf2], - threading: true, + single-threaded: false, keepalive: true } @@ -39,7 +39,9 @@ reactor GetUserInput { t.start() =} - reaction(user_response) -> user_input {= user_input.set(user_response.value) =} + reaction(user_response) -> user_input {= + user_input.set(user_response.value) + =} } /** Sends graphics updates to the pygame piano process */ @@ -49,7 +51,9 @@ reactor UpdateGraphics { state update_graphics = {= None =} # multiprocessing.connection.PipeConnection state pressed_keys = {= set() =} - reaction(update_graphics_pipe_init) {= self.update_graphics = update_graphics_pipe_init.value =} + reaction(update_graphics_pipe_init) {= + self.update_graphics = update_graphics_pipe_init.value + =} reaction(note) {= key_down, note_t = note.value @@ -71,7 +75,9 @@ reactor PlaySound { input note input play_sound_init - reaction(play_sound_init) {= self.fluidsynth, self.Note = play_sound_init.value =} + reaction(play_sound_init) {= + self.fluidsynth, self.Note = play_sound_init.value + =} reaction(note) {= # upon receiving a note, play or stop the note depending on if its a key down or key up. @@ -119,7 +125,9 @@ reactor TranslateKeyToNote { output note output gui_init - reaction(translate_init) -> gui_init {= gui_init.set(self.piano_keys) =} + reaction(translate_init) -> gui_init {= + gui_init.set(self.piano_keys) + =} reaction(user_input) -> note {= key_down, c = user_input.value @@ -183,7 +191,9 @@ reactor StartFluidSynth { /** Starts the GUI and triggers initialization of UpdateGraphics and GetUserInput reactors. */ reactor StartGui { - preamble {= import gui =} + preamble {= + import gui + =} input gui_init output user_input_pipe output update_graphics_pipe