Compare commits
25 commits
main
...
experiment
Author | SHA1 | Date | |
---|---|---|---|
3c5dec376d | |||
572340d5bd | |||
f3c3f15f3a | |||
5e4554710c | |||
0b2d42ed33 | |||
4545f47fff | |||
1e89b629f9 | |||
57768df01a | |||
cb8ad3c300 | |||
38762533c5 | |||
e8e6e7fbd9 | |||
6e3c4290db | |||
63e26e62fa | |||
b0b118c1b3 | |||
277c120b23 | |||
6bd0c26683 | |||
0f03753612 | |||
e3733b6f7e | |||
150d831652 | |||
b81b484bed | |||
317ba02ecc | |||
d6242bfb2e | |||
fd6a93b7db | |||
f4cb61c631 | |||
|
796b73dcee |
45 changed files with 18166 additions and 6475 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
platform-tools
|
66
Arduino/Bluetooth.ino
Normal file
66
Arduino/Bluetooth.ino
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include <bluefruit.h>
|
||||||
|
|
||||||
|
unit8_t uvindexvalue = 0x0;
|
||||||
|
#define UUID16_SVC_ENVIRONMENTAL_SENSING 0x181A
|
||||||
|
#define UUID16_CHR_UV_INDEX 0x2A76
|
||||||
|
|
||||||
|
BLEService enviromental_sensing_service = BLEService(UUID16_SVC_ENVIRONMENTAL_SENSING);
|
||||||
|
BLECharacteristic uv_index_characteristic = BLECharacteristic(UUID16_CHR_UV_INDEX);
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.beginn(115200);
|
||||||
|
delay(500);
|
||||||
|
Serial.println("Start!");
|
||||||
|
|
||||||
|
Bluefruit.begin();
|
||||||
|
Bluefruit.setName("Palm");
|
||||||
|
|
||||||
|
setupESService();
|
||||||
|
startAvd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
uvindexvalue = random(0, 11);
|
||||||
|
|
||||||
|
Serial.print("UV Index: ");
|
||||||
|
Serial.println(uvindexvalue);
|
||||||
|
|
||||||
|
if (uv_index_characteristic.indicate(&uvindexvalue, sizeof(uvindexvalue)))
|
||||||
|
{
|
||||||
|
Serial.print("Updated UV Index: ");
|
||||||
|
Serial.println(uvindexvalue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print("UV Index Indicate not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void startAvd(void)
|
||||||
|
{
|
||||||
|
Bluefruit.Advertising.addService(environmental_sensing_service);
|
||||||
|
|
||||||
|
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||||
|
Bluefruit.Advertising.addTxPower();
|
||||||
|
Bluefruit.Advertising.addName();
|
||||||
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
|
Bluefruit.Advertising.setInterval(32, 244);
|
||||||
|
Bluefruit.Advertising.setFastTimeout(30);
|
||||||
|
Bluefruit.Advertising.start(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupESService(void) {
|
||||||
|
environmental_sensing_service.begin();
|
||||||
|
|
||||||
|
uv_index_characteristic.setProperties(CHR_PROPS_INDICATE);
|
||||||
|
uv_index_characteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);
|
||||||
|
uv_index_characteristic.setFixedLen(1);
|
||||||
|
uv_index_characteristic.begin();
|
||||||
|
|
||||||
|
uv_index_characteristic.write(&uvindexvalue, sizeof(uvindexvalue));
|
||||||
|
}
|
59
BluetoothGUI/BluetoothGUI.js
Normal file
59
BluetoothGUI/BluetoothGUI.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* Bluetooth Test - Processing Side (In Chrome)
|
||||||
|
* Arduino to HM10 module to Google Chrome
|
||||||
|
* https://www.amazon.com/gp/product/B06WGZB2N4/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1
|
||||||
|
*
|
||||||
|
* p5.ble.js
|
||||||
|
https://yining1023.github.io/p5ble-website/
|
||||||
|
|
||||||
|
* kevin darrah
|
||||||
|
*
|
||||||
|
* Twitter: https://twitter.com/KDcircuits
|
||||||
|
* For inquiries or design services:
|
||||||
|
* https://www.kdcircuits.com
|
||||||
|
*
|
||||||
|
* License? Do whatever you want with this code - it's just a sample
|
||||||
|
*/
|
||||||
|
|
||||||
|
//globals
|
||||||
|
let blueToothCharacteristic;//this is a blu
|
||||||
|
let receivedValue = "";
|
||||||
|
|
||||||
|
let blueTooth;
|
||||||
|
let isConnected = false;
|
||||||
|
|
||||||
|
|
||||||
|
var millisecondTimerStart;
|
||||||
|
var oldColorPickerValue;
|
||||||
|
|
||||||
|
|
||||||
|
function setup() {
|
||||||
|
|
||||||
|
createCanvas(windowWidth, windowHeight);
|
||||||
|
|
||||||
|
|
||||||
|
// Create a p5ble class
|
||||||
|
console.log("setting up");
|
||||||
|
blueTooth = new p5ble();
|
||||||
|
|
||||||
|
const connectButton = createButton('Connect');
|
||||||
|
connectButton.mousePressed(connectToBle);
|
||||||
|
connectButton.position(15, 15);
|
||||||
|
|
||||||
|
const LEDonButton = createButton('LED ON');
|
||||||
|
LEDonButton.mousePressed(LEDon);
|
||||||
|
LEDonButton.position(15, 60);
|
||||||
|
|
||||||
|
const LEDoffButton = createButton('LED OFF');
|
||||||
|
LEDoffButton.mousePressed(LEDoff);
|
||||||
|
LEDoffButton.position(LEDonButton.x+LEDonButton.width+10, 60);
|
||||||
|
|
||||||
|
ledColorPicker = createColorPicker('#ff0000');
|
||||||
|
ledColorPicker.position(LEDoffButton.x+LEDoffButton.width+10, 60);
|
||||||
|
millisecondTimerStart = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
drawScreen();
|
||||||
|
}
|
7
BluetoothGUI/README.md
Normal file
7
BluetoothGUI/README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Another example
|
||||||
|
|
||||||
|
Sketch with HM-10 Bluetooth Module
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
- [Video](https://youtu.be/w_mRj5IlVpg) on Youtube
|
||||||
|
- Download [Link](https://www.kevindarrah.com/download/arduino_code/BluetoothGUI.zip) to this source files
|
48
BluetoothGUI/bluetooth.js
Normal file
48
BluetoothGUI/bluetooth.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
function connectToBle() {
|
||||||
|
// Connect to a device by passing the service UUID
|
||||||
|
blueTooth.connect(0xFFE0, gotCharacteristics);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A function that will be called once got characteristics
|
||||||
|
function gotCharacteristics(error, characteristics) {
|
||||||
|
if (error) {
|
||||||
|
console.log('error: ', error);
|
||||||
|
}
|
||||||
|
console.log('characteristics: ', characteristics);
|
||||||
|
blueToothCharacteristic = characteristics[0];
|
||||||
|
|
||||||
|
blueTooth.startNotifications(blueToothCharacteristic, gotValue, 'string');
|
||||||
|
|
||||||
|
|
||||||
|
isConnected = blueTooth.isConnected();
|
||||||
|
// Add a event handler when the device is disconnected
|
||||||
|
blueTooth.onDisconnected(onDisconnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// A function that will be called once got values
|
||||||
|
function gotValue(value) {
|
||||||
|
console.log('value: ', value);
|
||||||
|
if (value == 'Push Button') {
|
||||||
|
receivedValue = "Push Button Pressed";
|
||||||
|
} else {
|
||||||
|
receivedValue = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onDisconnected() {
|
||||||
|
console.log('Device got disconnected.');
|
||||||
|
isConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sendData(command) {
|
||||||
|
const inputValue = command;
|
||||||
|
if (!("TextEncoder" in window)) {
|
||||||
|
console.log("Sorry, this browser does not support TextEncoder...");
|
||||||
|
}
|
||||||
|
var enc = new TextEncoder(); // always utf-8
|
||||||
|
blueToothCharacteristic.writeValue(enc.encode(inputValue));
|
||||||
|
}
|
6
BluetoothGUI/commands.js
Normal file
6
BluetoothGUI/commands.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
function LEDon() {
|
||||||
|
sendData("LED Color" + ledColorPicker.value()+ "\n");
|
||||||
|
}
|
||||||
|
function LEDoff() {
|
||||||
|
sendData("LED OFF\n");
|
||||||
|
}
|
20
BluetoothGUI/drawScreen.js
Normal file
20
BluetoothGUI/drawScreen.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
function drawScreen() {
|
||||||
|
textSize(18);
|
||||||
|
if (isConnected) {
|
||||||
|
background(0, 255, 0);
|
||||||
|
text('Connected :)', 80, 15);
|
||||||
|
} else {
|
||||||
|
background(255, 0, 0);
|
||||||
|
textAlign(LEFT, TOP);
|
||||||
|
text('Disconnected :/', 80, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
text(receivedValue, 15, 40);
|
||||||
|
|
||||||
|
if(oldColorPickerValue != ledColorPicker.value() && millis()-millisecondTimerStart>50 && isConnected){
|
||||||
|
oldColorPickerValue = ledColorPicker.value();
|
||||||
|
sendData("LED Color" + ledColorPicker.value()+ "\n");
|
||||||
|
millisecondTimerStart = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
BluetoothGUI/index.html
Normal file
23
BluetoothGUI/index.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<!-- PLEASE NO CHANGES BELOW THIS LINE (UNTIL I SAY SO) -->
|
||||||
|
<script language="javascript" type="text/javascript" src="libraries/p5.min.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="libraries/p5.ble.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="bluetooth.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="commands.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="drawScreen.js"></script>
|
||||||
|
<script language="javascript" type="text/javascript" src="BluetoothGUI.js"></script>
|
||||||
|
<!-- OK, YOU CAN MAKE CHANGES BELOW THIS LINE AGAIN -->
|
||||||
|
<script src="p5.ble.min.js" type="text/javascript"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.2/addons/p5.dom.min.js"></script>
|
||||||
|
<!-- This line removes any default padding and style.
|
||||||
|
You might only need one of these values set. -->
|
||||||
|
<style> body { padding: 0; margin: 0; } </style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
11549
BluetoothGUI/libraries/p5.ble.js
Normal file
11549
BluetoothGUI/libraries/p5.ble.js
Normal file
File diff suppressed because it is too large
Load diff
3
BluetoothGUI/libraries/p5.min.js
vendored
Normal file
3
BluetoothGUI/libraries/p5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
BluetoothGUI/sketch.properties
Normal file
2
BluetoothGUI/sketch.properties
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mode=p5.js
|
||||||
|
mode.id=processing.mode.p5js.p5jsMode
|
34
README.md
34
README.md
|
@ -1,2 +1,34 @@
|
||||||
# leaky-ships
|
# leaky-ships
|
||||||
Battleship web app with react frontend and ASP .NET backend
|
Battleship web app with react frontend and ASP.NET Core backend
|
||||||
|
|
||||||
|
## Bluetooth
|
||||||
|
|
||||||
|
Download [Android SDK Platform-Tools](https://developer.android.com/studio/releases/platform-tools)
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
```
|
||||||
|
./platform-tools/adb pair 10.1.0.125:38407
|
||||||
|
./platform-tools/adb connect 10.1.0.125:39099
|
||||||
|
```
|
||||||
|
|
||||||
|
And debug Chrome:
|
||||||
|
```
|
||||||
|
chrome://inspect/#devices
|
||||||
|
```
|
||||||
|
|
||||||
|
Chrome flags to be enabled:
|
||||||
|
```
|
||||||
|
chrome://flags/#enable-experimental-web-platform-features
|
||||||
|
chrome://flags/#enable-web-bluetooth-new-permissions-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Dev tool to discover gatt services:
|
||||||
|
```
|
||||||
|
chrome://bluetooth-internals/#devices
|
||||||
|
```
|
||||||
|
Other resources:
|
||||||
|
|
||||||
|
- [Android Wireless Debugging](https://youtu.be/gyVZdZtIxnw?t=49) Tutorial
|
||||||
|
- [GATT Characteristics](https://btprodspecificationrefs.blob.core.windows.net/assigned-values/16-bit%20UUID%20Numbers%20Document.pdf) Document
|
||||||
|
- [Using Web BLE](https://youtu.be/TsXUcAKi790) Tutorial
|
||||||
|
- [Adafruit Feather nRF52 Bluefruit LE](https://www.berrybase.de/adafruit-feather-nrf52-bluefruit-le) Dev. Boards
|
11
backend/.gitignore
vendored
Normal file
11
backend/.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/log
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
2684
backend/package-lock.json
generated
Normal file
2684
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
34
backend/package.json
Normal file
34
backend/package.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "score-board-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node ./build/index.js",
|
||||||
|
"test": "nodemon --watch ./src/ --exec ts-node ./src/index.ts"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"formidable": "^2.0.1",
|
||||||
|
"socket.io": "^4.5.3",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/colors": "^1.2.1",
|
||||||
|
"@types/cors": "^2.8.12",
|
||||||
|
"@types/dotenv": "^8.2.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/formidable": "^2.0.5",
|
||||||
|
"@types/node": "^17.0.0",
|
||||||
|
"@types/uuid": "^8.3.3",
|
||||||
|
"nodemon": "^2.0.15",
|
||||||
|
"ts-node": "^10.4.0",
|
||||||
|
"typescript": "^4.5.2"
|
||||||
|
}
|
||||||
|
}
|
81
backend/src/index.ts
Normal file
81
backend/src/index.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import cors from 'cors';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import express from 'express';
|
||||||
|
import formidable from 'formidable';
|
||||||
|
import { mkdir, rename } from 'fs/promises';
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
if (!process.env.API_PORT)
|
||||||
|
process.exit(1)
|
||||||
|
|
||||||
|
// Start listening on port 5000
|
||||||
|
app.listen(+process.env.API_PORT, () => console.log(`Server running on: ${process.env.CORS_HOST}:${process.env.API_PORT}`))
|
||||||
|
|
||||||
|
// Middlewares
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cors({ origin: process.env.CORS_HOST }));
|
||||||
|
|
||||||
|
// File upload post route
|
||||||
|
app.post('/api/upload', (req, res) => {
|
||||||
|
const form = new formidable.IncomingForm();
|
||||||
|
form.parse(req, (err, fields, files) => {
|
||||||
|
if (!('filepath' in files.filetoupload) || !files.filetoupload?.filepath) {
|
||||||
|
res.write('Ups! Missing property "filetoupload" file');
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filename = files.filetoupload.originalFilename
|
||||||
|
const oldpath = files.filetoupload.filepath
|
||||||
|
const newdir = process.cwd() + '/upload/'
|
||||||
|
const newpath = newdir + filename
|
||||||
|
mkdir(newdir, { recursive: true })
|
||||||
|
.then(() => {
|
||||||
|
return rename(oldpath, newpath)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log('New Upload: ' + filename)
|
||||||
|
res.write('File uploaded and moved!');
|
||||||
|
res.end();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// - - - - -
|
||||||
|
|
||||||
|
|
||||||
|
import { createServer } from "http";
|
||||||
|
import { Server } from "socket.io";
|
||||||
|
|
||||||
|
const ws = {
|
||||||
|
CORS: 'http://localhost:3000',
|
||||||
|
PORT: 5001
|
||||||
|
}
|
||||||
|
const serverOptions = {
|
||||||
|
cors: {
|
||||||
|
origin: ws.CORS // React frontend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app2 = express();
|
||||||
|
const httpServer = createServer(app2);
|
||||||
|
const io = new Server(httpServer, serverOptions);
|
||||||
|
httpServer.listen(ws.PORT);
|
||||||
|
|
||||||
|
io.on("connection", (socket) => {
|
||||||
|
console.log(socket.id)
|
||||||
|
// console.log(socket)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
socket.on("test", (payload) => {
|
||||||
|
console.log("Got test:", payload)
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.emit('test2', 'lol')
|
||||||
|
});
|
101
backend/tsconfig.json
Normal file
101
backend/tsconfig.json
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||||
|
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./build", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
8593
frontend/package-lock.json
generated
8593
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -16,11 +16,15 @@
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/sass": "^1.43.1",
|
"@types/sass": "^1.43.1",
|
||||||
|
"@types/web-bluetooth": "^0.0.15",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-dropzone": "^14.2.3",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"sass": "^1.53.0",
|
"sass": "^1.53.0",
|
||||||
|
"socket.io-client": "^4.5.3",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^4.7.4",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|
BIN
frontend/public/fog/fog1.png
Normal file
BIN
frontend/public/fog/fog1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 659 KiB |
BIN
frontend/public/fog/fog2.png
Normal file
BIN
frontend/public/fog/fog2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 199 KiB |
BIN
frontend/public/fog/fog3.png
Normal file
BIN
frontend/public/fog/fog3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 631 KiB |
BIN
frontend/public/fog/fog4.png
Normal file
BIN
frontend/public/fog/fog4.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -1,77 +0,0 @@
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#game {
|
|
||||||
border: 5px solid orange;
|
|
||||||
position: relative;
|
|
||||||
height: 1200px;
|
|
||||||
width: 1200px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: repeat(12, 1fr);
|
|
||||||
grid-template-columns: repeat(12, 1fr);
|
|
||||||
grid-gap: 1px solid blue;
|
|
||||||
|
|
||||||
.missle {
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid red;
|
|
||||||
top: calc(50px + 100px * var(--y));
|
|
||||||
left: calc(50px + 100px* var(--x));
|
|
||||||
transform: translateX(-50%) translateY(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
height: 50px;
|
|
||||||
width: 50px;
|
|
||||||
align-self: center;
|
|
||||||
justify-self: center;
|
|
||||||
grid-column: var(--x);
|
|
||||||
grid-row: var(--y);
|
|
||||||
}
|
|
||||||
.r1 {
|
|
||||||
border: 2px solid blue;
|
|
||||||
}
|
|
||||||
.r2 {
|
|
||||||
border: 2px solid green;
|
|
||||||
}
|
|
||||||
.r3 {
|
|
||||||
border: 2px solid yellowgreen;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +1,23 @@
|
||||||
import { faXmark } from '@fortawesome/free-solid-svg-icons';
|
// import Gamefield from './components/Gamefield';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
// import Homepage from './components/Homepage';
|
||||||
import { CSSProperties } from 'react';
|
// import Homepage2 from './components/Homepage2';
|
||||||
import './App.scss';
|
// import SocketIO from './components/SocketIO';
|
||||||
|
// import Upload from './components/Upload';
|
||||||
|
import MyDropzone from './components/MyDropzone';
|
||||||
|
import './styles/App.scss';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
let elems: {
|
|
||||||
field: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
}[] = [],
|
|
||||||
count = 12;
|
|
||||||
for (let x = 1; x <= count; x++) {
|
|
||||||
for (let y = 1; y <= count; y++) {
|
|
||||||
elems.push({field: String.fromCharCode(64+x)+(y), x, y})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
{[1,2,3,4,5,6,11,12,13,14].map(num => <img src={`/svgs/${num}.svg`} alt={`${num}.svg`} />)}
|
{/* <Gamefield/> */}
|
||||||
<div id='game'>
|
{/* <Homepage/> */}
|
||||||
{elems.map(obj => <FontAwesomeIcon className={`${obj.field} r1`} style={{'--x': obj.x, '--y': obj.y} as CSSProperties} icon={faXmark} />)}
|
{/* <Homepage2/> */}
|
||||||
</div>
|
{/* <SocketIO/> */}
|
||||||
<p>
|
{/* <Upload /> */}
|
||||||
Edit <code>src/App.tsx</code> and save to reload.
|
<MyDropzone />
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="App-link"
|
|
||||||
href="https://www.freepik.com/free-vector/militaristic-ships-set-navy-ammunition-warship-submarine-nuclear-battleship-float-cruiser-trawler-gunboat-frigate-ferry_10704121.htm"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Battleships designed by macrovector
|
|
||||||
</a>
|
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
153
frontend/src/components/Bluetooth.tsx
Normal file
153
frontend/src/components/Bluetooth.tsx
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
function Bluetooth() {
|
||||||
|
const [startDisabled, setStartDisabled] = useState(true)
|
||||||
|
const [stopDisabled, setStopDisabled] = useState(true)
|
||||||
|
|
||||||
|
const deviceName = 'Chromecast Remote'
|
||||||
|
// ble UV Index
|
||||||
|
const bleService = 'environmental_sensing'
|
||||||
|
const bleCharacteristic = 'uv_index'
|
||||||
|
|
||||||
|
// ble Battery percentage
|
||||||
|
// const bleService = 'battery_service'
|
||||||
|
// const bleCharacteristic = 'battery_level'
|
||||||
|
|
||||||
|
// ble Manufacturer Name
|
||||||
|
// const bleService = 'device_information'
|
||||||
|
// const bleCharacteristic = 'manufacturer_name_string'
|
||||||
|
let bluetoothDeviceDetected: BluetoothDevice
|
||||||
|
let gattCharacteristic: BluetoothRemoteGATTCharacteristic
|
||||||
|
|
||||||
|
function isWebBluetoothEnabled() {
|
||||||
|
if (!navigator.bluetooth) {
|
||||||
|
alert('Web Bluetooth API is not available in this browser!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
function getDeviceInfo() {
|
||||||
|
const options = {
|
||||||
|
// acceptAllDevices: true,
|
||||||
|
filters: [
|
||||||
|
{ name: deviceName }
|
||||||
|
],
|
||||||
|
// optionalServices: ['battery_service'],
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Requesting Bluetooth Device...')
|
||||||
|
|
||||||
|
return navigator.bluetooth
|
||||||
|
.requestDevice(options)
|
||||||
|
.then(device => {
|
||||||
|
bluetoothDeviceDetected = device
|
||||||
|
console.log('> Name: ' + device.name)
|
||||||
|
device.addEventListener('gattserverdisconnected', onDisconnected)
|
||||||
|
})
|
||||||
|
.catch(error => console.log('Argh! ' + error))
|
||||||
|
}
|
||||||
|
function read() {
|
||||||
|
if (!isWebBluetoothEnabled())
|
||||||
|
return
|
||||||
|
return getDeviceInfo()
|
||||||
|
.then(connectGatt)
|
||||||
|
.then(_ => {
|
||||||
|
console.log('Reading UV Index...')
|
||||||
|
return gattCharacteristic.readValue()
|
||||||
|
})
|
||||||
|
.catch(error => console.log('Waiting to start reading: ' + error))
|
||||||
|
}
|
||||||
|
function connectGatt() {
|
||||||
|
if (bluetoothDeviceDetected.gatt && bluetoothDeviceDetected.gatt.connected && gattCharacteristic)
|
||||||
|
return Promise.resolve()
|
||||||
|
if (!bluetoothDeviceDetected || !bluetoothDeviceDetected.gatt)
|
||||||
|
return Promise.reject()
|
||||||
|
return bluetoothDeviceDetected.gatt.connect()
|
||||||
|
.then(server => {
|
||||||
|
console.log('Getting GATT Service...')
|
||||||
|
return server.getPrimaryService(bleService)
|
||||||
|
})
|
||||||
|
.then(service => {
|
||||||
|
console.log('Getting GATT Characteristic...')
|
||||||
|
return service.getCharacteristic(bleCharacteristic)
|
||||||
|
})
|
||||||
|
.then(characteristic => {
|
||||||
|
gattCharacteristic = characteristic
|
||||||
|
characteristic.addEventListener('characteristicvaluechanged', handleChangedValue)
|
||||||
|
|
||||||
|
setStartDisabled(false)
|
||||||
|
setStopDisabled(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function handleChangedValue(event: Event) {
|
||||||
|
const characteristic = event.target as BluetoothRemoteGATTCharacteristic
|
||||||
|
if (!characteristic.value) {
|
||||||
|
console.log('Characteristic undefined!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const value = characteristic.value.getUint8(0)
|
||||||
|
const now = new Date()
|
||||||
|
// Output the UV Index
|
||||||
|
console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} UV Index is ${value}`)
|
||||||
|
|
||||||
|
// Output the Battery percentage
|
||||||
|
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Battery percentage is ${value}`)
|
||||||
|
|
||||||
|
// Output the Manufacturer Name
|
||||||
|
// let decoder = new TextDecoder('utf-8')
|
||||||
|
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Manufacturer Name is ${decoder.decode(characteristic.value)}`)
|
||||||
|
}
|
||||||
|
function start() {
|
||||||
|
if (!isWebBluetoothEnabled())
|
||||||
|
return
|
||||||
|
gattCharacteristic.startNotifications()
|
||||||
|
.then(_ => {
|
||||||
|
console.log('Start reading...')
|
||||||
|
setStartDisabled(true)
|
||||||
|
setStopDisabled(false)
|
||||||
|
})
|
||||||
|
.catch(error => console.log('[ERROR] Start: ' + error))
|
||||||
|
}
|
||||||
|
function stop() {
|
||||||
|
if (!isWebBluetoothEnabled())
|
||||||
|
return
|
||||||
|
gattCharacteristic.stopNotifications()
|
||||||
|
.then(_ => {
|
||||||
|
console.log('Stop reading...')
|
||||||
|
setStartDisabled(false)
|
||||||
|
setStopDisabled(true)
|
||||||
|
})
|
||||||
|
.catch(error => console.log('[ERROR] Stop: ' + error))
|
||||||
|
}
|
||||||
|
function onDisconnected(event: Event) {
|
||||||
|
alert("Device Disconnected")
|
||||||
|
// console.log(event)
|
||||||
|
const device = event.target as BluetoothDevice
|
||||||
|
console.log(`Device "${device.name}" is disconnected.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
id="read"
|
||||||
|
className="bluetooth"
|
||||||
|
onClick={read}
|
||||||
|
>Connect with BLE device</button>
|
||||||
|
<button
|
||||||
|
id="start"
|
||||||
|
className="bluetooth"
|
||||||
|
disabled={startDisabled}
|
||||||
|
onClick={start}
|
||||||
|
>Start</button>
|
||||||
|
<button
|
||||||
|
id="stop"
|
||||||
|
className="bluetooth"
|
||||||
|
disabled={stopDisabled}
|
||||||
|
onClick={stop}
|
||||||
|
>Stop</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bluetooth
|
54
frontend/src/components/BorderTiles.tsx
Normal file
54
frontend/src/components/BorderTiles.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { CSSProperties, Dispatch, SetStateAction } from 'react';
|
||||||
|
import { borderCN, cornerCN, fieldIndex, isHit } from '../helpers';
|
||||||
|
import { HitDispatchType, HitType, TargetPreviewType, TargetType } from '../interfaces';
|
||||||
|
|
||||||
|
function BorderTiles({count, actions: {setTarget, setTargetPreview, hits, DispatchHits}}: {count: number, actions: {setTarget: Dispatch<SetStateAction<TargetType>>, setTargetPreview: Dispatch<SetStateAction<TargetPreviewType>>, hits: HitType[], DispatchHits: Dispatch<HitDispatchType>}}) {
|
||||||
|
let tilesProperties: {
|
||||||
|
key: number,
|
||||||
|
isGameTile: boolean,
|
||||||
|
classNameString: string,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
for (let y = 0; y < count+2; y++) {
|
||||||
|
for (let x = 0; x < count+2; x++) {
|
||||||
|
const key = fieldIndex(count, x, y);
|
||||||
|
const cornerReslt = cornerCN(count, x, y);
|
||||||
|
const borderType = cornerReslt ? cornerReslt : borderCN(count, x, y);
|
||||||
|
const isGameTile = x > 0 && x < count+1 && y > 0 && y < count+1;
|
||||||
|
const classNames = ['border-tile'];
|
||||||
|
if (borderType)
|
||||||
|
classNames.push('edge', borderType);
|
||||||
|
if (isGameTile)
|
||||||
|
classNames.push('game-tile');
|
||||||
|
const classNameString = classNames.join(' ')
|
||||||
|
tilesProperties.push({key, classNameString, isGameTile, x: x+1, y: y+1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <>
|
||||||
|
{tilesProperties.map(({key, classNameString, isGameTile, x, y}) => {
|
||||||
|
return <div
|
||||||
|
key={key}
|
||||||
|
className={classNameString}
|
||||||
|
style={{'--x': x, '--y': y} as CSSProperties}
|
||||||
|
onClick={() => {
|
||||||
|
if (!isGameTile && !isHit(hits, x, y).length)
|
||||||
|
return;
|
||||||
|
setTarget(t => {
|
||||||
|
if (t.x === x && t.y === y) {
|
||||||
|
DispatchHits({type: 'fireMissle', payload: {hit: (x+y)%2 !== 0, x, y}});
|
||||||
|
return { show: false, x, y };
|
||||||
|
} else {
|
||||||
|
return { show: true, x, y };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setTargetPreview(e => ({...e, newX: x, newY: y, shouldShow: isGameTile}))}
|
||||||
|
></div>
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorderTiles;
|
9
frontend/src/components/FogImages.tsx
Normal file
9
frontend/src/components/FogImages.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
function FogImages() {
|
||||||
|
return <>
|
||||||
|
<img className='fog left' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<img className='fog right' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<img className='fog middle' src={`/fog/fog4.png`} alt={`fog4.png`} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FogImages;
|
126
frontend/src/components/Gamefield.tsx
Normal file
126
frontend/src/components/Gamefield.tsx
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import { faCrosshairs } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { CSSProperties, useEffect, useReducer, useState } from 'react';
|
||||||
|
import Bluetooth from './Bluetooth';
|
||||||
|
import BorderTiles from './BorderTiles';
|
||||||
|
// import FogImages from './FogImages';
|
||||||
|
import HitElems from './HitElems';
|
||||||
|
import Labeling from './Labeling';
|
||||||
|
import Ships from './Ships';
|
||||||
|
import { hitReducer, initlialTarget, initlialTargetPreview, isHit } from '../helpers';
|
||||||
|
import { HitType, TargetPreviewType, TargetType } from '../interfaces';
|
||||||
|
|
||||||
|
function Gamefield() {
|
||||||
|
|
||||||
|
const count = 12;
|
||||||
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
|
const [targetPreview, setTargetPreview] = useState<TargetPreviewType>(initlialTargetPreview);
|
||||||
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
|
|
||||||
|
// handle visibility and position change of targetPreview
|
||||||
|
useEffect(() => {
|
||||||
|
const { newX, newY, shouldShow, appearOK, eventReady, show, x, y } = targetPreview;
|
||||||
|
const positionChange = !(x === newX && y === newY);
|
||||||
|
// if not ready or no new position
|
||||||
|
if (!eventReady || (!positionChange && show))
|
||||||
|
return;
|
||||||
|
if (show) {
|
||||||
|
// hide preview to change position when hidden
|
||||||
|
setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: false }));
|
||||||
|
} else if (shouldShow && appearOK && !isHit(hits, newX, newY).length) {
|
||||||
|
// BUT only appear again if it's supposed to (in case the mouse left over the edge) and ()
|
||||||
|
setTargetPreview(e => ({ ...e, appearOK: false, eventReady: false, show: true, x: newX, y: newY }));
|
||||||
|
}
|
||||||
|
}, [targetPreview, hits])
|
||||||
|
|
||||||
|
// enable targetPreview event again after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
if (targetPreview.eventReady)
|
||||||
|
return;
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setTargetPreview(e => ({ ...e, eventReady: true }));
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
// or abort if state has changed early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [targetPreview.eventReady]);
|
||||||
|
|
||||||
|
// approve targetPreview new position after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
// early return to start cooldown only when about to show up
|
||||||
|
if (!targetPreview.shouldShow)
|
||||||
|
return;
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setTargetPreview(e => ({ ...e, appearOK: true }));
|
||||||
|
}, 350);
|
||||||
|
|
||||||
|
// or abort if movement is repeated early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='gamefield'>
|
||||||
|
<Bluetooth />
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
className="App-link"
|
||||||
|
onClick={() => { navigator.clipboard.writeText("chrome://flags/#enable-experimental-web-platform-features") }}
|
||||||
|
// target="_blank"
|
||||||
|
style={{ "cursor": "pointer" }}
|
||||||
|
// rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Step 1
|
||||||
|
</span>
|
||||||
|
{" "}
|
||||||
|
<span
|
||||||
|
className="App-link"
|
||||||
|
onClick={() => { navigator.clipboard.writeText("chrome://flags/#enable-web-bluetooth-new-permissions-backend") }}
|
||||||
|
// target="_blank"
|
||||||
|
style={{ "cursor": "pointer" }}
|
||||||
|
// rel="noopener noreferrer"
|
||||||
|
|
||||||
|
>
|
||||||
|
Step 2
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
|
||||||
|
{/* Bordes */}
|
||||||
|
<BorderTiles count={count} actions={{ setTarget, setTargetPreview, hits, DispatchHits }} />
|
||||||
|
|
||||||
|
{/* Collumn lettes and row numbers */}
|
||||||
|
<Labeling count={count} />
|
||||||
|
|
||||||
|
{/* Ships */}
|
||||||
|
<Ships />
|
||||||
|
|
||||||
|
<HitElems hits={hits} />
|
||||||
|
|
||||||
|
{/* Fog images */}
|
||||||
|
{/* <FogImages /> */}
|
||||||
|
<div className={`hit-svg target ${target.show ? 'show' : ''}`} style={{ '--x': target.x, '--y': target.y } as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
|
</div>
|
||||||
|
<div className={`hit-svg target-preview ${targetPreview.show && (target.x !== targetPreview.x || target.y !== targetPreview.y) ? 'show' : ''}`} style={{ '--x': targetPreview.x, '--y': targetPreview.y } as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={faCrosshairs} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to reload.
|
||||||
|
</p> */}
|
||||||
|
<a
|
||||||
|
className="App-link"
|
||||||
|
href="https://www.freepik.com/free-vector/militaristic-ships-set-navy-ammunition-warship-submarine-nuclear-battleship-float-cruiser-trawler-gunboat-frigate-ferry_10704121.htm"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<p>Battleships designed by macrovector</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Gamefield
|
17
frontend/src/components/HitElems.tsx
Normal file
17
frontend/src/components/HitElems.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { faBurst, faXmark } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import { HitType } from '../interfaces';
|
||||||
|
|
||||||
|
function HitElems({hits}: {hits: HitType[]}) {
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{hits.map(({hit, x, y}, i) =>
|
||||||
|
<div key={i} className='hit-svg' style={{'--x': x, '--y': y} as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={hit ? faBurst : faXmark} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HitElems;
|
87
frontend/src/components/Homepage.tsx
Normal file
87
frontend/src/components/Homepage.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||||
|
import '../styles/home.scss'
|
||||||
|
|
||||||
|
function Homepage() {
|
||||||
|
|
||||||
|
const floorClient = (number: number) => Math.floor(number / 50)
|
||||||
|
|
||||||
|
const [columns, setColumns] = useState(floorClient(document.body.clientWidth))
|
||||||
|
const [rows, setRows] = useState(floorClient(document.body.clientHeight))
|
||||||
|
const [params, setParams] = useState({ columns, rows, quantity: columns * rows })
|
||||||
|
const [position, setPosition] = useState([0, 0])
|
||||||
|
const [active, setActve] = useState(false)
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
setColumns(floorClient(document.body.clientWidth))
|
||||||
|
setRows(floorClient(document.body.clientHeight))
|
||||||
|
}
|
||||||
|
handleResize()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setParams({ columns, rows, quantity: columns * rows })
|
||||||
|
}, 500)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [columns, rows])
|
||||||
|
|
||||||
|
const createTiles = useMemo(() => {
|
||||||
|
|
||||||
|
const colors = [
|
||||||
|
'rgb(229, 57, 53)',
|
||||||
|
'rgb(253, 216, 53)',
|
||||||
|
'rgb(244, 81, 30)',
|
||||||
|
'rgb(76, 175, 80)',
|
||||||
|
'rgb(33, 150, 243)',
|
||||||
|
'rgb(156, 39, 176)'
|
||||||
|
]
|
||||||
|
|
||||||
|
function createTile(index: number) {
|
||||||
|
|
||||||
|
const x = index % params.columns
|
||||||
|
const y = Math.floor(index / params.columns)
|
||||||
|
const xDiff = (x - position[0]) / 20
|
||||||
|
const yDiff = (y - position[1]) / 20
|
||||||
|
const pos = (Math.sqrt(xDiff * xDiff + yDiff * yDiff)).toFixed(2)
|
||||||
|
|
||||||
|
const doEffect = (posX: number, posY: number) => {
|
||||||
|
if (active)
|
||||||
|
return
|
||||||
|
setPosition([posX, posY])
|
||||||
|
setActve(true)
|
||||||
|
|
||||||
|
const xDiff = (x: number) => (x - posX) / 20
|
||||||
|
const yDiff = (y: number) => (y - posY) / 20
|
||||||
|
const pos = (x: number, y: number) => Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
|
||||||
|
const diagonals = [pos(0, 0), pos(params.columns, 0), pos(0, params.rows), pos(params.columns, params.rows)]
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setActve(false)
|
||||||
|
setCount(e => e + 1)
|
||||||
|
}, Math.max(...diagonals) * 1000 + 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={'tile ' + (active ? 'active' : '')}
|
||||||
|
style={{ '--delay': pos + 's' } as CSSProperties}
|
||||||
|
onClick={() => doEffect(x, y)}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows, '--bg-color-1': colors[count % colors.length], '--bg-color-2': colors[(count + 1) % colors.length] } as CSSProperties}>
|
||||||
|
{Array.from(Array(params.quantity)).map((_tile, index) => createTile(index))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [params, position, active, count])
|
||||||
|
|
||||||
|
return createTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Homepage
|
92
frontend/src/components/Homepage2.tsx
Normal file
92
frontend/src/components/Homepage2.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||||
|
import '../styles/home2.scss'
|
||||||
|
|
||||||
|
function Homepage2() {
|
||||||
|
|
||||||
|
const floorClient = (number: number) => Math.floor(number / 50)
|
||||||
|
|
||||||
|
const [columns, setColumns] = useState(floorClient(document.body.clientWidth))
|
||||||
|
const [rows, setRows] = useState(floorClient(document.body.clientHeight))
|
||||||
|
const [params, setParams] = useState({ columns, rows, quantity: columns * rows })
|
||||||
|
const [position, setPosition] = useState([0, 0])
|
||||||
|
const [active, setActve] = useState(false)
|
||||||
|
const [action, setAction] = useState(false)
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
setColumns(floorClient(document.body.clientWidth))
|
||||||
|
setRows(floorClient(document.body.clientHeight))
|
||||||
|
}
|
||||||
|
handleResize()
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
setParams({ columns, rows, quantity: columns * rows })
|
||||||
|
}, 500)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [columns, rows])
|
||||||
|
|
||||||
|
const createTiles = useMemo(() => {
|
||||||
|
|
||||||
|
const sentences = [
|
||||||
|
'Ethem ...',
|
||||||
|
'hat ...',
|
||||||
|
'lange ...',
|
||||||
|
'Hörner 🐂',
|
||||||
|
'Grüße von Mallorca 🌊 🦦 ☀️'
|
||||||
|
]
|
||||||
|
|
||||||
|
function createTile(index: number) {
|
||||||
|
|
||||||
|
const x = index % params.columns
|
||||||
|
const y = Math.floor(index / params.columns)
|
||||||
|
const xDiff = (x - position[0]) / 20
|
||||||
|
const yDiff = (y - position[1]) / 20
|
||||||
|
const pos = (Math.sqrt(xDiff * xDiff + yDiff * yDiff)).toFixed(2)
|
||||||
|
|
||||||
|
const doEffect = (posX: number, posY: number) => {
|
||||||
|
if (action)
|
||||||
|
return
|
||||||
|
setPosition([posX, posY])
|
||||||
|
setActve(e => !e)
|
||||||
|
setAction(true)
|
||||||
|
|
||||||
|
const xDiff = (x: number) => (x - posX) / 50
|
||||||
|
const yDiff = (y: number) => (y - posY) / 50
|
||||||
|
const pos = (x: number, y: number) => Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
|
||||||
|
const diagonals = [pos(0, 0), pos(params.columns, 0), pos(0, params.rows), pos(params.columns, params.rows)]
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setAction(false)
|
||||||
|
if (active)
|
||||||
|
setCount(e => e + 1)
|
||||||
|
}, Math.max(...diagonals) * 1000 + 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={'tile ' + (active ? 'active' : 'inactive')}
|
||||||
|
style={{ '--delay': pos + 's' } as CSSProperties}
|
||||||
|
onClick={() => doEffect(x, y)}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows } as CSSProperties}>
|
||||||
|
<div className="center-div">
|
||||||
|
<h1 className={'headline ' + (!active ? 'active' : 'inactive')}>{sentences[count % sentences.length]}</h1>
|
||||||
|
</div>
|
||||||
|
{Array.from(Array(params.quantity)).map((_tile, index) => createTile(index))}
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}, [params, position, active, action, count])
|
||||||
|
|
||||||
|
return createTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Homepage2
|
29
frontend/src/components/Labeling.tsx
Normal file
29
frontend/src/components/Labeling.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { CSSProperties } from 'react'
|
||||||
|
import { fieldIndex } from '../helpers';
|
||||||
|
import { FieldType } from '../interfaces';
|
||||||
|
|
||||||
|
function Labeling({count}: {count: number}) {
|
||||||
|
let elems: (FieldType & {
|
||||||
|
orientation: string
|
||||||
|
})[] = [];
|
||||||
|
for (let x = 0; x < count; x++) {
|
||||||
|
elems.push(
|
||||||
|
// Up
|
||||||
|
{field: String.fromCharCode(65+x), x: x+2, y: 1, orientation: 'up'},
|
||||||
|
// Left
|
||||||
|
{field: (x+1).toString(), x: 1, y: x+2, orientation: 'left'},
|
||||||
|
// Bottom
|
||||||
|
{field: String.fromCharCode(65+x), x: x+2, y: count+2, orientation: 'bottom'},
|
||||||
|
// Right
|
||||||
|
{field: (x+1).toString(), x: count+2, y: x+2, orientation: 'right'}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
elems = elems.sort((a, b) => fieldIndex(count, a.x, a.y)-fieldIndex(count, b.x, b.y));
|
||||||
|
return <>
|
||||||
|
{elems.map(({field, x, y, orientation}, i) =>
|
||||||
|
<span key={i} className={`label ${orientation} ${field}`} style={{'--x': x, '--y': y} as CSSProperties}>{field}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Labeling;
|
28
frontend/src/components/MyDropzone.tsx
Normal file
28
frontend/src/components/MyDropzone.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import {useCallback, useState} from 'react'
|
||||||
|
import {useDropzone} from 'react-dropzone'
|
||||||
|
import { readFile } from '../helpers'
|
||||||
|
|
||||||
|
function MyDropzone() {
|
||||||
|
const [txt, settxt] = useState('Empty!')
|
||||||
|
const onDrop = useCallback(async (acceptedFiles: any) => {
|
||||||
|
// Do something with the files
|
||||||
|
console.log(acceptedFiles)
|
||||||
|
settxt(await readFile(acceptedFiles[0]))
|
||||||
|
}, [])
|
||||||
|
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...getRootProps()} className={classNames('filedropper', isDragActive ? 'dragging' : 'standby')}>
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
{
|
||||||
|
isDragActive ?
|
||||||
|
<p>Drop the files here ...</p> :
|
||||||
|
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||||
|
}
|
||||||
|
{txt}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyDropzone
|
19
frontend/src/components/Ships.tsx
Normal file
19
frontend/src/components/Ships.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { CSSProperties } from 'react'
|
||||||
|
|
||||||
|
function Ships() {
|
||||||
|
let shipIndexes: number[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
shipIndexes.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{shipIndexes.map(i =>
|
||||||
|
<div key={i} className={`ship s${i}`} style={{'--x': i+3} as CSSProperties}>
|
||||||
|
<img src={`/svgs/${i}.svg`} alt={`${i}.svg`}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ships;
|
30
frontend/src/components/SocketIO.tsx
Normal file
30
frontend/src/components/SocketIO.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { io } from 'socket.io-client';
|
||||||
|
|
||||||
|
function SocketIO() {
|
||||||
|
const socket = io("ws://localhost:5001");
|
||||||
|
socket.on('test2', (warst) => {
|
||||||
|
console.log('Test2:', warst, socket.id)
|
||||||
|
})
|
||||||
|
socket.on("connect", () => {
|
||||||
|
console.log(socket.connected); // true
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.emit('test', "warst")
|
||||||
|
socket.emit('test', "tsra")
|
||||||
|
socket.emit('test', "1234")
|
||||||
|
// socket.disconnect()
|
||||||
|
}, 1000)
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("test", () => {
|
||||||
|
console.log("Got test1234"); // false
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
console.log(socket.connected); // false
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>SocketIO</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SocketIO
|
44
frontend/src/components/Upload.tsx
Normal file
44
frontend/src/components/Upload.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
function Upload() {
|
||||||
|
const [file, setFile] = useState('');
|
||||||
|
const [filename, setFilename] = useState('');
|
||||||
|
|
||||||
|
// function handleChange
|
||||||
|
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
console.log(e.target.files);
|
||||||
|
if (!e.target.files?.length)
|
||||||
|
return
|
||||||
|
const file = e.target.files[0]
|
||||||
|
console.log(URL.createObjectURL(e.target.files[0]))
|
||||||
|
setFile(URL.createObjectURL(e.target.files[0]));
|
||||||
|
setFilename(e.target.files[0].name);
|
||||||
|
|
||||||
|
|
||||||
|
const url = 'http://localhost:5000/api/upload';
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('filetoupload', file);
|
||||||
|
|
||||||
|
console.log(file)
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(url, config)
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response.body)
|
||||||
|
})
|
||||||
|
.catch(error => console.log(error))
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Add Image:</h2>
|
||||||
|
<input type="file" className='inputt' onChange={handleChange} />
|
||||||
|
<img src={file} alt={filename || 'No selection'} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Upload
|
67
frontend/src/helpers.ts
Normal file
67
frontend/src/helpers.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { HitType, HitDispatchType } from "./interfaces";
|
||||||
|
|
||||||
|
export const borderCN = (count: number, x: number, y: number) => {
|
||||||
|
if (x === 0)
|
||||||
|
return 'left';
|
||||||
|
if (y === 0)
|
||||||
|
return 'top';
|
||||||
|
if (x === count+1)
|
||||||
|
return 'right';
|
||||||
|
if (y === count+1)
|
||||||
|
return 'bottom';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
export const cornerCN = (count: number, x: number, y: number) => {
|
||||||
|
if (x === 0 && y === 0)
|
||||||
|
return 'left-top-corner';
|
||||||
|
if (x === count+1 && y === 0)
|
||||||
|
return 'right-top-corner';
|
||||||
|
if (x === 0 && y === count+1)
|
||||||
|
return 'left-bottom-corner';
|
||||||
|
if (x === count+1 && y === count+1)
|
||||||
|
return 'right-bottom-corner';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
export const fieldIndex = (count: number, x: number, y: number) => y*(count+2)+x;
|
||||||
|
export const hitReducer = (formObject: HitType[], action: HitDispatchType) => {
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case 'fireMissle': {
|
||||||
|
const result = [...formObject, action.payload];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return formObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const initlialTarget = {
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export const initlialTargetPreview = {
|
||||||
|
newX: 0,
|
||||||
|
newY: 0,
|
||||||
|
shouldShow: false,
|
||||||
|
appearOK: false,
|
||||||
|
eventReady: true,
|
||||||
|
show: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export const isHit = (hits: HitType[], x: number, y: number) => hits.filter(h => h.x === x && h.y === y);
|
||||||
|
export function readFile(file: File): Promise<string> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const reader = new FileReader()
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
if (!e.target)
|
||||||
|
return
|
||||||
|
const text = (e.target.result)
|
||||||
|
if (typeof text !== 'string')
|
||||||
|
return
|
||||||
|
resolve(text)
|
||||||
|
};
|
||||||
|
reader.readAsText(file)
|
||||||
|
})
|
||||||
|
}
|
30
frontend/src/interfaces.ts
Normal file
30
frontend/src/interfaces.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export interface TargetType {
|
||||||
|
show: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
};
|
||||||
|
export interface TargetPreviewType {
|
||||||
|
newX: number,
|
||||||
|
newY: number,
|
||||||
|
shouldShow: boolean,
|
||||||
|
appearOK: boolean,
|
||||||
|
eventReady: boolean,
|
||||||
|
show: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
};
|
||||||
|
export interface FieldType {
|
||||||
|
field: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
export interface HitType {
|
||||||
|
hit: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface fireMissle { type: 'fireMissle', payload: { x: number, y: number, hit: boolean } };
|
||||||
|
interface removeMissle { type: 'removeMissle', payload: { x: number, y: number, hit: boolean } };
|
||||||
|
|
||||||
|
export type HitDispatchType = fireMissle | removeMissle;
|
214
frontend/src/styles/App.scss
Normal file
214
frontend/src/styles/App.scss
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
@use './mixins/display' as *;
|
||||||
|
@use './mixins/effects' as *;
|
||||||
|
@import './mixins/variables';
|
||||||
|
|
||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: $theme;
|
||||||
|
min-height: 100vh;
|
||||||
|
@include flex-col;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-frame {
|
||||||
|
// border: 1px solid orange;
|
||||||
|
// position: relative;
|
||||||
|
$height: 945px;
|
||||||
|
$width: $height;
|
||||||
|
height: $height;
|
||||||
|
width: $width;
|
||||||
|
max-height: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
grid-template-rows: .75fr repeat(12, 1fr) .75fr;
|
||||||
|
grid-template-columns: .75fr repeat(12, 1fr) .75fr;
|
||||||
|
// grid-gap: 1px solid blue;
|
||||||
|
|
||||||
|
> .label {
|
||||||
|
grid-column: var(--x);
|
||||||
|
grid-row: var(--y);
|
||||||
|
}
|
||||||
|
> .border-tile {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid blue;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
grid-column: var(--x);
|
||||||
|
grid-row: var(--y);
|
||||||
|
|
||||||
|
&.edge {
|
||||||
|
border: 1px solid gray;
|
||||||
|
}
|
||||||
|
@include gradient-edge;
|
||||||
|
}
|
||||||
|
> :not(.border) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
// border: 1px solid red;
|
||||||
|
}
|
||||||
|
> span {
|
||||||
|
vertical-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ship {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
@include flex-col;
|
||||||
|
align-items: center;
|
||||||
|
// justify-content: center;
|
||||||
|
border: 1px solid yellow;
|
||||||
|
grid-row: var(--x);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
// height: 90%;
|
||||||
|
width: 90%;
|
||||||
|
// object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.s1 {
|
||||||
|
grid-column: 3 / 7;
|
||||||
|
}
|
||||||
|
&.s2 {
|
||||||
|
grid-column: 3 / 5;
|
||||||
|
}
|
||||||
|
&.s3 {
|
||||||
|
grid-column: 3 / 6;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-top: -2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.s4 {
|
||||||
|
grid-column: 3 / 6;
|
||||||
|
}
|
||||||
|
&.s5 {
|
||||||
|
grid-column: 3 / 6;
|
||||||
|
}
|
||||||
|
&.s6 {
|
||||||
|
grid-column: 3 / 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hit-svg {
|
||||||
|
// border: 2px solid blue;
|
||||||
|
|
||||||
|
// height: 50px;
|
||||||
|
// width: 50px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
@include flex;
|
||||||
|
// align-items: center;
|
||||||
|
// align-self: center;
|
||||||
|
// justify-self: center;
|
||||||
|
grid-column: var(--x);
|
||||||
|
grid-row: var(--y);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 25%;
|
||||||
|
|
||||||
|
&.fa-burst {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.target {
|
||||||
|
color: red;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&.target-preview {
|
||||||
|
color: orange;
|
||||||
|
opacity: 0;
|
||||||
|
@include transition(.2s);
|
||||||
|
|
||||||
|
}
|
||||||
|
&.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.r2 {
|
||||||
|
border: 2px solid green;
|
||||||
|
}
|
||||||
|
.r3 {
|
||||||
|
border: 2px solid yellowgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fog {
|
||||||
|
width: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
grid-area: 1 / 1 / -1 / -1;
|
||||||
|
align-self: flex-start;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
grid-area: 1 / 1 / -1 / -1;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.middle {
|
||||||
|
grid-area: 4 / 4 / -4 / -4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputt {
|
||||||
|
background-color: #61dafb;
|
||||||
|
&:hover {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filedropper {
|
||||||
|
@include transition(.2s);
|
||||||
|
transition-property: background-color;
|
||||||
|
background-color: transparent;
|
||||||
|
border: .25em dashed #fff2;
|
||||||
|
border-radius: 1em;
|
||||||
|
width: 40vw;
|
||||||
|
min-height: 10vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
&.dragging {
|
||||||
|
background-color: #fff1;
|
||||||
|
border: .25em dashed #00f8;
|
||||||
|
}
|
||||||
|
}
|
36
frontend/src/styles/home.scss
Normal file
36
frontend/src/styles/home.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
@use './mixins/effects' as *;
|
||||||
|
|
||||||
|
#tiles {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--columns), 1fr);
|
||||||
|
grid-template-rows: repeat(var(--rows), 1fr);
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
background-color: var(--bg-color-1);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
animation: bright .3s forwards;
|
||||||
|
animation-delay: var(--delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bright {
|
||||||
|
0% {
|
||||||
|
background-color: var(--bg-color-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
|
||||||
|
background-color: var(--bg-color-2);
|
||||||
|
filter: brightness(130%);
|
||||||
|
outline: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
|
||||||
|
background-color: var(--bg-color-2);
|
||||||
|
}
|
||||||
|
}
|
94
frontend/src/styles/home2.scss
Normal file
94
frontend/src/styles/home2.scss
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
@use './mixins/effects' as *;
|
||||||
|
$g1: rgb(98, 0, 234);
|
||||||
|
$g2: rgb(236, 64, 122);
|
||||||
|
|
||||||
|
#tiles {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--columns), 1fr);
|
||||||
|
grid-template-rows: repeat(var(--rows), 1fr);
|
||||||
|
background: linear-gradient(to right, $g1, $g2, $g1);
|
||||||
|
background-size: 200%;
|
||||||
|
animation: background-pan 10s linear infinite;
|
||||||
|
|
||||||
|
.tile {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
background-color: rgb(20, 20, 20);
|
||||||
|
inset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
animation: hide .2s forwards;
|
||||||
|
animation-delay: var(--delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
opacity: 0;
|
||||||
|
animation: show .2s forwards;
|
||||||
|
animation-delay: var(--delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center-div {
|
||||||
|
position: absolute;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: inherit;
|
||||||
|
|
||||||
|
.headline {
|
||||||
|
margin: auto;
|
||||||
|
font-size: 5em;
|
||||||
|
background-color: rgba(20, 20, 20, 0.2);
|
||||||
|
padding: .25em .5em;
|
||||||
|
border-radius: .25em;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
opacity: 1;
|
||||||
|
animation: hide 2s forwards;
|
||||||
|
// animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inactive {
|
||||||
|
opacity: 0;
|
||||||
|
animation: show 2s forwards;
|
||||||
|
// animation-delay: 1s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes background-pan {
|
||||||
|
from {
|
||||||
|
background-position: 0% center;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
background-position: -200% center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hide {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
11
frontend/src/styles/mixins/display.scss
Normal file
11
frontend/src/styles/mixins/display.scss
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
@mixin flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
@mixin flex-col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
@mixin flex-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
53
frontend/src/styles/mixins/effects.scss
Normal file
53
frontend/src/styles/mixins/effects.scss
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
@mixin transition($timing) {
|
||||||
|
-moz-transition: $timing;
|
||||||
|
-webkit-transition: $timing;
|
||||||
|
-o-transition: $timing;
|
||||||
|
-ms-transition: $timing;
|
||||||
|
transition: $timing;
|
||||||
|
}
|
||||||
|
@mixin appear($timing) {
|
||||||
|
animation: appear $timing forwards;
|
||||||
|
}
|
||||||
|
@keyframes appear {
|
||||||
|
From {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
To {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin gradient-edge {
|
||||||
|
&.left-top-corner {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, right bottom,
|
||||||
|
left top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.right-top-corner {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left bottom,
|
||||||
|
right top, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.left-bottom-corner {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, right top,
|
||||||
|
left bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.right-bottom-corner {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left top,
|
||||||
|
right bottom, color-stop(0, rgba(0,0,0,1)), color-stop(0.5, rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, right top,
|
||||||
|
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.right {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left top,
|
||||||
|
right top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.top {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left bottom,
|
||||||
|
left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
&.bottom {
|
||||||
|
-webkit-mask-image: -webkit-gradient(linear, left top,
|
||||||
|
left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0)));
|
||||||
|
}
|
||||||
|
}
|
1
frontend/src/styles/mixins/variables.scss
Normal file
1
frontend/src/styles/mixins/variables.scss
Normal file
|
@ -0,0 +1 @@
|
||||||
|
$theme: #282c34;
|
Loading…
Add table
Add a link
Reference in a new issue