Skip to content
Snippets Groups Projects
Commit 0af207e8 authored by seba's avatar seba
Browse files

working demo

parent 66aea0f7
No related branches found
No related tags found
No related merge requests found
Showing
with 1743 additions and 86 deletions
# cocobotdemo # Board Generator
## Getting started ## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps. run `bash start_docker.sh` to create 2 docker containers:
* an instance of the GOLMI server
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! * the web application to generate random boards.
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://gitup.uni-potsdam.de/seniorencler/cocobotdemo.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://gitup.uni-potsdam.de/seniorencler/cocobotdemo/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License the web-GUI of the generator will be available at "http://localhost:5000"
For open source projects, say how it is licensed.
## Project status once you're finished, simply stop the containers (`docker stop $(docker ps -q)` to stop all running containers on your machine)
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
File added
File added
File added
File added
File added
File added
File added
import os
from flask import Flask
from flask_cors import CORS
from app.blueprints.home import home
import app.events as evt
def create_app(debug=False):
"""Create an application."""
app = Flask(__name__)
app.debug = debug
evt.init_app(app)
CORS(app)
app.config["golmi_host"] = os.environ.get("GOLMI_HOST")
app.config["golmi_port"] = os.environ.get("GOLMI_PORT")
app.config["golmi_pswd"] = os.environ.get("GOLMI_PSWD")
app.register_blueprint(home)
return app
\ No newline at end of file
File added
File added
File added
File added
from io import BytesIO
import json
import os
import sys
import uuid
from flask import render_template, send_file, current_app, request, Blueprint, render_template
import requests
from app.events import socketio
from golmi_client import GolmiClient
home = Blueprint('home', __name__, template_folder='templates')
clients = dict()
sid2room_id = dict()
with open("piece_variables.json", encoding="utf-8") as infile:
PIECE_VARIABLES = json.load(infile)
CONFIG = {"width": 25.0, "height": 25.0, "move_step": 0.5, "prevent_overlap": False}
OBJS = {
"X": {
"id_n": 0,
"type": "X",
"x": 0,
"y": 0,
"rotation": 0,
"color": ["red", "#ff0000", [255, 0, 0]],
"block_matrix": [
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 0, 0]
]
},
"Y": {
"id_n": 0,
"type": "Y",
"x": 0,
"y": 0,
"rotation": 0,
"color": ["red", "#ff0000", [255, 0, 0]],
"block_matrix": [
[0, 0, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 1, 1],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
},
"Z": {
"id_n": 0,
"type": "Z",
"x": 0,
"y": 0,
"rotation": 0,
"color": ["red", "#ff0000", [255, 0, 0]],
"block_matrix": [
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 0, 0],
[0, 0, 0, 0, 0]
]
}
}
COLORS = {
"GREEN": ["green", "#008000", [0, 128, 0]],
"PURPLE": ["purple", "#800080", [128, 0, 128]],
"PINK" : ["pink", "#ffc0cb", [255, 192, 203]]
}
@socketio.on('click')
def sample_preview_board(config):
golmi_server = f'http://{current_app.config["golmi_host"]}:{current_app.config["golmi_port"]}'
room_id = config["room_id"]
if config["placing"] is True:
req = requests.get(
f"{golmi_server}/slurk/{room_id}/state"
)
print(f"{golmi_server}/slurk/{room_id}/state")
state = req.json()
obj = OBJS[config["shape"]]
obj["color"] = COLORS[config["color"]]
x = config["coordinates"]["x"] // config["coordinates"]["block_size"]
y = config["coordinates"]["y"] // config["coordinates"]["block_size"]
obj["x"] = x -2
obj["y"] = y -2
id_n = str(len(state["objs"]))
obj["id_n"] = id_n
state["objs"][id_n] = obj
sid2room_id[request.sid] = room_id
client = clients[room_id]
client.load_state(state)
print(client)
print(room_id)
else:
req = requests.get(
f'{golmi_server}/slurk/{room_id}/{config["coordinates"]["x"]}/{config["coordinates"]["y"]}/{config["coordinates"]["block_size"]}'
)
if req.ok is not True:
print("Could not retrieve gripped piece")
piece = req.json()
if piece:
piece = list(piece.values())[0]
socketio.emit("gripped", {
"color": piece["color"][0].upper(),
"shape": piece["type"],
})
# @socketio.on('change')
# def change(data):
# golmi_server = f'http://{current_app.config["golmi_host"]}:{current_app.config["golmi_port"]}'
# room_id = data["room_id"]
# req = requests.get(
# f"{golmi_server}/slurk/{room_id}/state"
# )
# state = req.json()
# req = requests.get(
# f'{golmi_server}/slurk/{room_id}/gripped/mouse'
# )
# piece = req.json()
# if piece:
# id_n = list(piece.keys())[0]
# obj = piece[id_n]
# if data["color"] in COLORS:
# print("change color")
# new_color = COLORS[data["color"]]
# obj["color"] = new_color
# if data["shape"] in OBJS:
# print("change shape")
# new_shape = data["shape"]
# new_matrix = OBJS[data["shape"]]["block_matrix"]
# obj["block_matrix"] = new_matrix
# obj["type"] = new_shape
# state["objs"][id_n] = obj
# state["grippers"]["mouse"]["gripped"][id_n] = obj
# print(state)
# print(f"{golmi_server}/slurk/{room_id}/state")
# client = clients[room_id]
# client.load_state(state)
@socketio.on('disconnect')
def disconnection():
this_room = sid2room_id.pop(request.sid)
clients.pop(this_room)
@home.route('/')
def main():
room_id = str(uuid.uuid4())
golmi_address = f'http://{current_app.config["golmi_host"]}:{current_app.config["golmi_port"]}'
golmi_pswd = current_app.config["golmi_pswd"]
client = GolmiClient()
clients[room_id] = client
if client.connected is False:
print(f"connecting to Golmi server on {golmi_address}", flush=True)
client.run(golmi_address, room_id, golmi_pswd)
client.load_config(CONFIG)
return render_template(
"home.html",
room_id=room_id,
golmi_host=golmi_address,
golmi_pswd=golmi_pswd
)
from flask_socketio import SocketIO
socketio = SocketIO(ping_interval=5, ping_timeout=120)
def init_app(app):
socketio.init_app(app)
\ No newline at end of file
$(document).ready(function () {
/**
* Local controller. Can connect to one or more models. In each model, a gripper is created
* at connection. If a gripperId is passed, an existing gripper with this id is assigned.
* Otherwise a new gripper is added with the given id or alternatively the session id.
* The user's keystrokes are used to control the assigned gripper(s) and any gripped object.
* @param {optional Array of [socket, gripperId], where gripperId can be null, default:null} modelSockets
*/
this.LocalKeyController = class LocalKeyController {
constructor(modelSockets=null) {
// array of model socket-gripperId pairs that are controlled
this.models = modelSockets ? modelSockets : new Array();
// assign functions to key codes: [function for keydown, function for keyup, down?]
// grip and flip are one-time actions, move and rotate are looped
this.keyAssignment = {
13: [this.grip, null, false], // Enter
32: [this.grip, null, false], // Space
37: [this.moveLeft, this.stopMove, false], // arrow left
38: [this.moveUp, this.stopMove, false], // arrow up
39: [this.moveRight, this.stopMove, false], // arrow right
40: [this.moveDown, this.stopMove, false], // arrow down
65: [this.rotateLeft, this.stopRotate, false], // a
68: [this.rotateRight, this.stopRotate, false], // d
83: [this.flip, null, false], // s
87: [this.flip, null, false] // w
};
// Set up key listeners
this._initKeyListener();
}
// --- (Un)Subscribing models ---
/**
* Subscribe a new model. Only one subscription per model is allowed,
* meaning in one model, only one gripper can be controlled!
* A gripper is controlled once the model sends an 'attach_gripper'
* event. See requestGripper for manually adding a gripper.
* @param {socket of the model server to notify} socket
*/
attachModel(socket) {
// make sure not to subscribe a model-gripper pair twice
for (let [existingSocket, g] of this.models) {
if (existingSocket.id == socket.id) {
return;
}
}
// use the id authoratively assigned by the model
socket.on("attach_gripper", (assignedId) => {
this.models.push([socket, assignedId]);
});
}
/**
* Remove a model from the internal list of models to notify. Remove the associated gripper.
* @param {socket of the model API to unsubscribe} socket
*/
detachModel(socket) {
// remove any occurence of the socket
for (let i = 0; i < this.models.length; i++) {
if (this.models[i][0].id == socket.id) {
this.models[i][0].emit("remove_gripper", this.models[i][1]);
this.models.splice(i, 1);
}
}
}
// --- Notifying subscribed models ---
/**
* Notifies all subscribed models that a "grip" should be attempted.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
grip(thisArg, loop) {
// send an event to each model
thisArg.models.forEach(([socket, grId]) => {
socket.emit("grip", {"id": grId, "loop":loop});
});
}
/**
* Request stopping ongoing looped gripping.
*/
stopGrip(thisArg) {
// send a request to each subscribed model
thisArg.models.forEach(([socket, grId]) => {
socket.emit("stop_grip", {"id":grId});
});
}
/**
* Notify models to move the gripper 1 unit to the left.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
moveLeft(thisArg, loop) { thisArg._moveGr(-1, 0, loop); }
/**
* Notify models to move the gripper 1 unit up.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
moveUp(thisArg, loop) { thisArg._moveGr(0, -1, loop); }
/**
* Notify models to move the gripper 1 unit to the right.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
moveRight(thisArg, loop) { thisArg._moveGr(1, 0, loop); }
/**
* Notify models to move the gripper 1 unit down.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
moveDown(thisArg, loop) { thisArg._moveGr(0, 1, loop); }
/**
* Helper function to notify models to move the gripper 1 block in a specified direction.
* @param {number of blocks to move in x direction} dx
* @param {number of blocks to move in y direction} dy
* @param {set to true to request a looped action on the model side} loop
*/
_moveGr(dx, dy, loop) {
this.models.forEach(([socket, grId]) => {
socket.emit("move", {"id": grId, "dx": dx, "dy": dy, "loop": loop});
});
}
/**
* Request stopping an ongoing looped movement.
*/
stopMove(thisArg) {
thisArg.models.forEach(([socket, grId]) => {
socket.emit("stop_move", {"id": grId});
});
}
/**
*
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
rotateLeft(thisArg, loop) { thisArg._rotate(-1, loop); }
/**
*
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
rotateRight(thisArg, loop) { thisArg._rotate(1, loop); }
/**
* Helper function to notify models to rotate a gripped object in a specified direction.
* @param {number of units to turn. Pass negative value for leftwards rotation} direction
* @param {set to true to request a looped action on the model side} loop
*/
_rotate(direction, loop) {
this.models.forEach(([socket, grId]) => {
socket.emit("rotate", {"id":grId, "direction":direction, "loop":loop});
});
}
/**
* Stop an ongoing looped rotation.
*/
stopRotate(thisArg) {
thisArg.models.forEach(([socket, grId]) => {
socket.emit("stop_rotate", {"id":grId});
});
}
/**
* Notify models to flip a gripped object on a specified axis.
* @param {reference to LocalKeyController instance (this)} thisArg
* @param {set to true to request a looped action on the model side} loop
*/
flip(thisArg, loop) {
thisArg.models.forEach(([socket, grId]) => {
socket.emit("flip", {"id":grId, "loop":loop});
});
}
/**
* Request stopping ongoing looped mirroring.
*/
stopFlip(thisArg) {
thisArg.models.forEach(([socket, grId]) => {
socket.emit("stop_flip", {"id":grId});
});
}
// --- Reacting to user events ---
/**
* Start fresh, delete any keys remembered as currently pressed.
*/
resetKeys(){
for (let key of Object.keys(this.keyAssignment)) {
// set property pressed to false for each key
this.keyAssignment[key][2] = false;
}
}
/**
* Register the key listeners to allow gripper manipulation.
* Notifies the associated models.
*/
_initKeyListener() {
$(document).keydown( e => {
if (this._downAssigned(e.keyCode)) {
// only progress if the key is not already in state "down"
if (!this._isDown(e.keyCode)) {
// Change the state to "down". This is done for all keys, not
// just loopable ones, to prevent the keydown event from
// firing repeatedly if the key is held.
this.keyAssignment[e.keyCode][2] = true;
// execute the function assigned to the keydown event
let loopable = this._upAssigned(e.keyCode);
this.keyAssignment[e.keyCode][0](this, loopable);
}
}
});
$(document).keyup( e => {
// check if a function is assigned. Only execute if the key was remembered as "down"
if (this._upAssigned(e.keyCode) && this._isDown(e.keyCode)) {
// execute the function assigned to the keyup event
this.keyAssignment[e.keyCode][1](this);
}
// change the state to "up"
if (this._registered(e.keyCode)) {
this.keyAssignment[e.keyCode][2] = false;
}
});
}
_registered(keyCode) {
return this.keyAssignment[keyCode] ? true : false;
}
/**
* Check whether a function is assigned to the keydown event of a given key code.
* @param {int, code of the key in question} keyCode
* @return bool, true signifying a function is assigned to keydown
*/
_downAssigned(keyCode) {
return this._registered(keyCode) && this.keyAssignment[keyCode][0] ? true : false;
}
/**
* Check whether a function is assigned to the keyup event of a given key code.
* @param {int, code of the key in question} keyCode
* @return bool, true signifying a function is assigned to keyup
*/
_upAssigned(keyCode) {
return this._registered(keyCode) && this.keyAssignment[keyCode][1] ? true : false;
}
/**
* Check whether a key is currently in "down" state aka currently pressed.
* @return bool, true if the key is "down"
*/
_isDown(keyCode) {
return this._registered(keyCode) && this.keyAssignment[keyCode][2] ? true : false;
}
}; // class LocalKeyController end
}); // on document ready end
\ No newline at end of file
$(document).ready(function () {
/**
* Extends the generic View class by implementations of the drawing
* functions.
* This class works with three stacked canvas layers: the 'background'
* holds a grid and optionally marks target positions for objects, the
* 'object' layer has all static objects (objects not currently gripped,
* and finally the 'gripper' layer displays grippers as well as objects
* held by the grippers.
* The reasoning behind this separation is that these components need to
* be redrawn in varying frequency: while the background is static unless
* the game configuration (board dimensions, etc.) or object target
* positions change, the objects are meant to be manipulated throughout an
* interaction and might have to be redrawn several times. The gripper as
* well as the currently gripped objects however change continuously and
* have to be redrawn constantly.
* @param {Socket io connection to the server} modelSocket
* @param {reference to the canvas DOM element to draw the background to}
bgCanvas
* @param {reference to the canvas DOM element to draw the static objects
to} objCanvas
* @param {reference to the canvas DOM element to draw grippers and
gripped objects to} grCanvas
*/
this.GiverLayerView = class GiverLayerView extends document.View {
constructor(modelSocket, bgCanvas, objCanvas, grCanvas) {
super(modelSocket);
// Three overlapping canvas
this.bgCanvas = bgCanvas;
this.objCanvas = objCanvas;
this.grCanvas = grCanvas;
// array holding the currently gripped objects
this.grippedObjs = new Array();
// Empty the canvas
this.clear();
}
// Canvas width in pixels. Assumes all 3 canvas are the same size
get canvasWidth() {
return this.bgCanvas.width;
}
get canvasHeight() {
return this.bgCanvas.height;
}
// --- drawing functions --- //
/**
* Remove any old drawings.
*/
clear() {
// clear all three canvas
this.clearBg();
this.clearObj();
this.clearGr();
}
/**
* Remove old drawings from the background layer.
*/
clearBg() {
let ctx = this.bgCanvas.getContext("2d");
ctx.clearRect(0, 0, this.bgCanvas.width, this.bgCanvas.height);
}
/**
* Remove old drawings from the object layer.
*/
clearObj() {
let ctx = this.objCanvas.getContext("2d");
ctx.clearRect(0, 0, this.objCanvas.width, this.objCanvas.height);
}
/**
* Remove old drawings from the gripper layer.
*/
clearGr() {
let ctx = this.grCanvas.getContext("2d");
ctx.clearRect(0, 0, this.grCanvas.width, this.grCanvas.height);
}
/**
* Draws a grid black on white as the background.
*/
drawBg() {
let ctx = this.bgCanvas.getContext("2d");
// important: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/clearRect
// using beginPath() after clear() prevents odd behavior
ctx.beginPath();
ctx.fillStyle = "white";
ctx.lineStyle = "black";
ctx.lineWidth = 1;
// white rectangle for background
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
/** no grid for now
// horizontal lines
for (let row = 0; row <= this.rows; row++) {
ctx.moveTo(0, row * this.blockSize);
ctx.lineTo(this.canvasWidth, row * this.blockSize);
}
// vertical lines
for (let col = 0; col <= this.cols; col++) {
ctx.moveTo(col * this.blockSize, 0);
ctx.lineTo(col * this.blockSize, this.canvasHeight);
}
// draw to the screen
ctx.stroke();
**/
// add targets
for (const target of Object.values(this.targets)) {
// for use cases where targets can be gripped: don't draw
// gripped targets on this layer, should be drawn on the
// gripper layer instead
if (target.gripped) { continue; }
let blockMatrix = target.block_matrix;
// call drawing helper functions with additional infos
let params = {
x: target.x,
y: target.y,
color: "Cornsilk"
}
// there should be only a single target (which is then overdrawn by the actual piece)
this._drawBlockObj(ctx, blockMatrix, params);
// draw bounding box around target (there should be only a single one in this experiment)
this._drawBB(ctx, blockMatrix, params, "red");
}
}
/**
* Redraw the background.
* In contrast to drawBg(), this function assumes the background has
* been drawn before and the old drawing needs to be removed first.
*/
redrawBg() {
this.clearBg();
this.drawBg();
}
/**
* Draw the (static) objects.
*/
drawObjs() {
let ctx = this.objCanvas.getContext("2d");
ctx.beginPath();
// draw each object
for (const obj of Object.values(this.objs)) {
// skip any gripped object here
let blockMatrix = obj.block_matrix;
// call drawing helper functions with additional infos
let params = {
x: obj.x,
y: obj.y,
color: obj.color[1]
}
this._drawBlockObj(ctx, blockMatrix, params);
}
}
/**
* Redraw the (static) objects.
* In contrast to drawObjs(), this function assumes the objects have
* been drawn before and the old drawing needs to be removed first.
*/
redrawObjs() {
this.clearObj();
this.drawObjs();
}
/**
* Draw the gripper object and, if applicable, the gripped object.
* The gripper is used to navigate on the canvas and move objects.
*/
drawGr() {
let ctx = this.grCanvas.getContext("2d");
ctx.beginPath()
for (const [grId, gripper] of Object.entries(this.grippers)) {
// draw any gripped object first (i.e. 'below' the gripper)
if (gripper.gripped) {
for (const [grippedId, grippedObj] of Object.entries(gripper.gripped)) {
let blockMatrix = grippedObj.block_matrix;
if (grId == "init"){
var highlight_color = "black";
} else {
var highlight_color = "green";
}
let params = {
x: grippedObj.x,
y: grippedObj.y,
color: grippedObj.color,
highlight: highlight_color // highlight a gripped object
}
this._drawBlockObj(ctx,
blockMatrix,
params);
if (grId == "init"){
this._drawBB(ctx, blockMatrix, params, "red");
}
else{
this._drawBB(ctx, blockMatrix, params, "green");
}
}
}
}
}
/**
* Redraw the gripper object and, if applicable, the gripped object.
* In contrast to drawGr(), this function expects the gripper has been
* drawn before and the old drawing needs to be removed first.
*/
redrawGr() {
this.clearGr();
this.drawGr();
}
// --- draw helper functions ---
_drawBB(ctx, bMatrix, params, color) {
// Draw blocks
for (let i=0; i< bMatrix.length; i++) {
this._drawUpperBorder(ctx, params.x + i, params.y, color);
}
for (let i=0; i< bMatrix.length; i++) {
this._drawLowerBorder(ctx, params.x + i, params.y + bMatrix.length -1, color);
}
for (let i=0; i< bMatrix.length; i++) {
this._drawLeftBorder(ctx, params.x, params.y + i, color);
}
for (let i=0; i< bMatrix.length; i++) {
this._drawRightBorder(ctx, params.x + bMatrix[0].length -1, params.y + i, color);
}
}
_drawBlockObj(ctx, bMatrix, params) {
// Draw blocks
for (let r=0; r<bMatrix.length;r++) {
bMatrix[r].forEach((block, c) => {
if (block) { // draw if matrix field contains a 1
let x = params.x + c;
let y = params.y + r;
this._drawBlock(ctx, x, y, params.color);
// draw object borders
if (this._isUpperBorder(bMatrix, c, r)) {
this._drawUpperBorder(ctx, x, y, params.highlight);
}
if (this._isLowerBorder(bMatrix, c, r)) {
this._drawLowerBorder(ctx, x, y, params.highlight);
}
if (this._isLeftBorder(bMatrix, c, r)) {
this._drawLeftBorder(ctx, x, y, params.highlight);
}
if (this._isRightBorder(bMatrix, c, r)) {
this._drawRightBorder(ctx, x, y, params.highlight);
}
}
});
}
}
_drawBlock(ctx, x, y, color, lineColor="grey", lineWidth=1) {
// --- config ---
ctx.fillStyle = color;
let px = this._toPxl(x);
let py = this._toPxl(y);
let w = Math.abs(px - this._toPxl(x+1));
let h = Math.abs(py - this._toPxl(y+1));
ctx.fillRect(px, py, w, h);
}
_drawUpperBorder(
ctx, x, y, highlight=false, borderColor="black", borderWidth=2) {
this._drawBorder(ctx, x, y, x+1, y, highlight, borderColor, borderWidth);
}
_drawLowerBorder(
ctx, x, y, highlight=false, borderColor="black", borderWidth=2) {
this._drawBorder(ctx, x, y+1, x+1, y+1, highlight, borderColor, borderWidth);
}
_drawLeftBorder(
ctx, x, y, highlight=false, borderColor="black", borderWidth=2) {
this._drawBorder(ctx, x, y, x, y+1, highlight, borderColor, borderWidth);
}
_drawRightBorder(
ctx, x, y, highlight=false, borderColor="black", borderWidth=2) {
this._drawBorder(ctx, x+1, y, x+1, y+1, highlight, borderColor, borderWidth);
}
_drawBorder(ctx, x1, y1, x2, y2, highlight=false, borderColor="black",
borderWidth=2) {
// --- config ---
// for no highlight, shadowBlur is set to 0 (= invisible)
ctx.shadowBlur = highlight ? 5 : 0;
ctx.shadowColor = highlight;
ctx.lineStyle = borderColor;
ctx.lineWidth = borderWidth;
ctx.beginPath();
ctx.moveTo(this._toPxl(x1), this._toPxl(y1));
ctx.lineTo(this._toPxl(x2), this._toPxl(y2));
ctx.stroke();
ctx.shadowBlur = 0;
}
_toPxl(coord) {
return coord * this.blockSize;
}
_isUpperBorder(blockMatrix, column, row) {
// true if 'row' is the top row OR there is no block above
return row == 0 || blockMatrix[row-1][column] == 0;
}
_isLowerBorder(blockMatrix, column, row) {
// true if 'row' is the bottom row OR there is no block below
return row == (blockMatrix.length-1) ||
blockMatrix[row+1][column] == 0;
}
_isLeftBorder(blockMatrix, column, row) {
// true if 'column' is the leftmost column OR there is no block
// to the left
return column == 0 || blockMatrix[row][column-1] == 0;
}
_isRightBorder(blockMatrix, column, row) {
// true if 'column' is the rightmost column OR there is no block
// to the right
return column == (blockMatrix[row].length-1) ||
blockMatrix[row][column+1] == 0;
}
}; // class LayerView end
}); // on document ready end
\ No newline at end of file
This diff is collapsed.
$(document).ready(function () {
/**
* Abstract interface class. Separates the interface into background, objects and grippers
* which concrete implementations of this view might want to draw separately to improve the
* performance (the components are static to varying degrees).
* While drawing functions lack implementations, internal data structures and basic communication
* with the model are already sketched out in this class.
* @param {Socket io connection to the server} modelSocket
*/
this.View = class View {
constructor(modelSocket) {
this.socket = modelSocket;
this._initSocketEvents();
// Configuration. Is assigned at startDrawing()
this.cols; // canvas width in blocks
this.rows; // canvas height in blocks
// Current state
this.objs = new Object();
this.grippers = new Object();
this.targets = new Object();
}
/**
* Start listening to events emitted by the model socket. After this
* initialization, the view reacts to model updates.
*/
_initSocketEvents() {
// new state -> redraw object and gripper layer,
// if targets are given, redraw background
this.socket.on("update_state", (state) => {
if (state["grippers"] && state["objs"]) {
this.onUpdateState(state) // hook
this.grippers = state["grippers"];
this.objs = state["objs"];
this.redrawGr();
this.redrawObjs();
} else {
console.log("Error: Received state from model does not " +
"have the right format. " +
"Expected keys 'grippers' and 'objs'.");
}
if (state["targets"]) {
this.targets = state["targets"];
this.redrawBg();
}
});
// new gripper state -> redraw grippers
this.socket.on("update_grippers", (grippers) => {
this.grippers = grippers;
this.redrawGr();
});
// new object state -> redraw objects
this.socket.on("update_objs", (objs) => {
this.onUpdateObjects(objs); // hook
this.objs = objs;
this.redrawObjs();
});
// new target state -> redraw background
this.socket.on("update_targets", (targets) => {
this.onUpdateTargets(targets); // hook
this.targets = targets;
this.redrawBg();
});
// new configuration -> save values and redraw everything
this.socket.on("update_config", (config) => {
this._loadConfig(config);
this.redraw();
});
}
// --- getter / setter --- //
// canvas width in pixels.
get canvasWidth() {
console.log("get canvasWidth() at View: not implemented");
return undefined;
}
get canvasHeight() {
console.log("get canvasHeight() at View: not implemented");
return undefined;
}
get blockSize() {
return this.canvasWidth/this.cols;
}
// --- drawing functions --- //
/**
* Remove any old drawings.
*/
clear() {
console.log("clear() at View: not implemented");
}
/**
* Draw background, objects, gripper.
*/
draw() {
this.drawBg();
this.drawGr();
this.drawObjs();
}
/**
* Redraw everything.
* In contrast to draw(), this function assumes the game has been drawn in the past
* and the old drawing needs to be removed first.
*/
redraw() {
this.clear();
this.draw();
}
/**
* Draw a background to the game.
*/
drawBg() {
console.log("drawBg() at View: not implemented");
}
/**
* Redraw the background.
* In contrast to drawBg(), this function assumes the background has been drawn in the past
* and the old drawing needs to be removed first.
*/
redrawBg() {
console.log("redrawBg() at View: not implemented");
}
/**
* Draw the (static) objects.
*/
drawObjs() {
console.log(`drawObjs() at View: not implemented`);
}
/**
* Redraw the (static) objects.
* In contrast to drawObjs(), this function assumes the objects have been drawn in the past
* and the old drawing needs to be removed first.
*/
redrawObjs() {
console.log(`redrawObjs() at View: not implemented`);
}
/**
* Draw the gripper object (and, depending on the implementation, the gripped object too)
* The Gripper is used to navigate on the canvas and move objects.
*/
drawGr() {
console.log("drawGr() at View: not implemented");
}
/**
* Redraw the gripper object (and, depending on the implementation, the gripped object too).
* In contrast to drawGr(), this function assumes the gripper has been drawn in the past
* and the old drawing needs to be removed first.
*/
redrawGr() {
console.log("redrawGr() at View: not implemented");
}
onUpdateObjects(objs) {
console.log(`onUpdateObjects() at View: not implemented`);
}
onUpdateTargets(targets) {
console.log(`onUpdateTargets() at View: not implemented`);
}
onUpdateState(state) {
console.log(`onUpdateState() at View: not implemented`);
}
/**
* Loads a configuration received from the model. The values are saved since the configuration is
* not expected to change frequently.
* If no configuration is passed, it is requested from the model.
* Implemented as an async function to make sure the configuration is complete before
* subsequent steps (i.e. drawing) are made.
* @param {config object, obtained from the model} config
*/
_loadConfig(config) {
// Save all relevant values
this.cols = config.width;
this.rows = config.height;
}
}; // class View end
}); // on document ready end
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Board Generator</title>
</head>
<style>
#viewport {
position: relative;
}
#viewport canvas {
position: absolute;
}
canvas{
background-color: transparent;
border: 1px solid #000000;
}
.input_field {
width: 100%;
padding: 5px;
display: flex
}
.input_field a{
width: 50%;
}
</style>
<body>
<div class="ui padded container" style="width: 90%">
<div class="ui segment" style="margin: 10px">
<h1 id="question" style="text-transform:uppercase; text-align: center;">
<font color="purple">Generate random boards for GOLMI</font>
</h1>
</div>
<div class="ui segment" style="margin: 10px; display: flex">
<div class="ui segment" style="margin: 10px; width: 60%;">
<div id="viewport" style="height: 600px; width: 600px; margin: auto;">
<canvas id="background" width="600px" height="600px"></canvas>
<canvas id="objects" width="600px" height="600px"></canvas>
<canvas id="gripper" width="600px" height="600px"></canvas>
</div>
</div>
<div class="ui segment" style="margin: 10px; width: 40%;">
<div class="input_field">
<a>Shape: </a>
<select class="ui selection dropdown" id="shape">
<option value=""> </option>
<option value="X"> X </option>
<option value="Y"> Y </option>
<option value="Z"> Z </option>
</select>
</div>
<div class="input_field">
<a>Color: </a>
<select class="ui selection dropdown" id="color">
<option value=""> </option>
<option value="GREEN"> GREEN </option>
<option value="PURPLE"> PURPLE </option>
<option value="PINK"> PINK </option>
</select>
</div>
<div style="display: flex">
<input id="preview" type="submit" class="ui basic fluid purple button" value="PREVIEW" style="margin: 10px; width: 50%">
<input id="clear" type="submit" class="ui basic fluid blue button" value="CLEAR" style="margin: 10px; width: 50%">
</div>
<hr>
<div class="input_field">
<a>Placing a new object: </a>
<div class="ui toggle checkbox" style="width: 50%">
<input type="checkbox" id="placing">
<label>True</label>
</div>
</div>
</div>
</div>
</div>
<!-- You MUST include jQuery before Fomantic -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.css">
<script src="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.8/dist/semantic.min.js"></script>
<script type="text/javascript" src="//cdn.socket.io/4.4.1/socket.io.min.js"></script>
<script src="{{ url_for('static', filename='js/view/View.js') }}"></script>
<script src="{{ url_for('static', filename='js/view/GiverLayerView.js') }}"></script>
<script src="{{ url_for('static', filename='js/view/LView.js') }}"></script>
<script>
let golmi_socket = null
let layerView = null
function start_golmi(url, password) {
// expect same as backend e.g. the default "http://127.0.0.1:5000";
console.log(`Connect to ${url}`)
// --- create a golmi_socket --- //
// don't connect yet
golmi_socket = io(url, {
auth: { "password": password }
});
// debug: print any messages to the console
localStorage.debug = 'golmi_socket.io-client:golmi_socket';
// --- view --- //
// Get references to the three canvas layers
let bgLayer = document.getElementById("background");
let objLayer = document.getElementById("objects");
let grLayer = document.getElementById("gripper");
grLayer.onclick = (event) => {
color = $("#color").val()
shape = $("#shape").val()
placing = $("#placing").is(':checked')
socket.emit("click", {
color: color,
shape: shape,
placing: placing,
room_id: "{{ room_id }}",
coordinates: {"x": event.offsetX, "y": event.offsetY, "block_size": layerView.blockSize}
});
}
// Set up the view js, this also sets up key listeners
layerView = new document.GiverLayerView(golmi_socket, bgLayer, objLayer, grLayer);
// --- golmi_socket communication --- //
golmi_socket.on("connect", () => {
console.log("Connected to model server");
});
golmi_socket.on("disconnect", () => {
console.log("Disconnected from model server");
});
golmi_socket.on("joined_room", (data) => {
console.log(`Joined room ${data.room_id} as client ${data.client_id}`);
})
// for debugging: log all events
golmi_socket.onAny((eventName, ...args) => {
console.log(eventName, args);
});
}
$(document).ready(function () {
$('.ui.dropdown').dropdown({"clearable": true});
$('#color').dropdown('setting', 'onChange', function(){change()});
$('#shape').dropdown('setting', 'onChange', function(){change()});
function change(){
if ($("#placing").is(':checked') === false){
socket.emit("change", {
"shape": $('#shape').val(),
"color": $('#color').val(),
"room_id": "{{ room_id }}",
})
}
}
socket = io.connect();
socket.on("connect", () => {
console.log(socket.id)
})
start_golmi( "{{ golmi_host }}", "{{ golmi_pswd }}")
golmi_socket.connect();
golmi_socket.emit("join", { "room_id": "{{ room_id }}" });
$("#clear").click( function(e){
$("#color").dropdown("clear");
$("#shape").dropdown("clear");
})
socket.on("gripped", (data) => {
$("#color").dropdown("clear");
$("#shape").dropdown("clear");
$("#color").dropdown("set selected", data.color);
$("#shape").dropdown("set selected", data.shape);
})
})
</script>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment