Compare commits

...
Sign in to create a new pull request.

25 commits

Author SHA1 Message Date
3c5dec376d
Reading filedropper 2022-11-23 20:33:00 +01:00
572340d5bd
MyDropzone does work 2022-11-09 21:44:19 +01:00
f3c3f15f3a
Add file upload functionality 2022-11-09 21:34:20 +01:00
5e4554710c
Add functionality to Socket.IO client 2022-10-28 22:21:55 +02:00
0b2d42ed33
Add Socket.IO server 2022-10-28 22:21:37 +02:00
4545f47fff
Fix fog images pointer events 2022-10-27 19:15:44 +02:00
1e89b629f9
Add Socket.IO client 2022-10-26 00:15:33 +02:00
57768df01a
Quickfix 2022-10-26 00:13:20 +02:00
cb8ad3c300
Reveal grid effect finished 2022-10-24 23:56:11 +02:00
38762533c5
Colorfull shockwave effect 2022-10-24 20:36:50 +02:00
e8e6e7fbd9
Adding copy of files for grid outline effect 2022-10-24 18:24:39 +02:00
6e3c4290db
Better functioning grid effect with rainbow colors 2022-10-24 18:16:57 +02:00
63e26e62fa
Implement CSS grid click effect 2022-10-24 00:35:28 +02:00
b0b118c1b3
Added example sketch for HM-10 module 2022-09-25 15:04:30 +02:00
277c120b23
Improved Bluetooth sketches and docs 2022-09-25 14:45:22 +02:00
6bd0c26683
Using Bluetooth on mobile 2022-09-25 00:28:29 +02:00
0f03753612
targetPreview missle fix
`targetPreview` did not show up when moving the mouse from an empty to a missle field and then back to the empty field.

The cause of the problem was no update of the position when going over the missle field and when going back resultend in no `potisionChange` which reslted into an early return.

Fixed this by adding the dependency `show` for early return caused by no `positionChange` which blocks the early return when `targetPreview` is not being `show`-n.
2022-08-16 17:47:53 +02:00
e3733b6f7e
Manuel missle fire 2022-08-16 11:03:12 +02:00
150d831652
Refactoring 2022-08-15 23:16:31 +02:00
b81b484bed
Finished targetPreview
Reverted back to react logic instead of scss logic and improved performance by partially showing the preview icon instead of letting it follow the mouse directly.
2022-08-14 02:37:58 +02:00
317ba02ecc
Working experimental targetPreview 2022-08-12 19:38:47 +02:00
d6242bfb2e
New functional features and fog images 2022-08-10 23:25:34 +02:00
fd6a93b7db
Move styles and add mixins 2022-08-10 23:06:41 +02:00
f4cb61c631
Working CSS grid 2022-08-09 03:06:15 +02:00
aronmal
796b73dcee
Update README.md 2022-08-07 01:50:55 +02:00
45 changed files with 18166 additions and 6475 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
platform-tools

66
Arduino/Bluetooth.ino Normal file
View 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));
}

View 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
View 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
View 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
View file

@ -0,0 +1,6 @@
function LEDon() {
sendData("LED Color" + ledColorPicker.value()+ "\n");
}
function LEDoff() {
sendData("LED OFF\n");
}

View 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
View 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>

File diff suppressed because it is too large Load diff

3
BluetoothGUI/libraries/p5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
mode=p5.js
mode.id=processing.mode.p5js.p5jsMode

View file

@ -1,2 +1,34 @@
# 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
View 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

File diff suppressed because it is too large Load diff

34
backend/package.json Normal file
View 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
View 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
View 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. */
}
}

File diff suppressed because it is too large Load diff

View file

@ -16,11 +16,15 @@
"@types/react-dom": "^18.0.5",
"@types/react-router-dom": "^5.3.3",
"@types/sass": "^1.43.1",
"@types/web-bluetooth": "^0.0.15",
"classnames": "^2.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"sass": "^1.53.0",
"socket.io-client": "^4.5.3",
"typescript": "^4.7.4",
"web-vitals": "^2.1.4"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -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;
}
}

View file

@ -1,39 +1,23 @@
import { faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { CSSProperties } from 'react';
import './App.scss';
// import Gamefield from './components/Gamefield';
// import Homepage from './components/Homepage';
// import Homepage2 from './components/Homepage2';
// import SocketIO from './components/SocketIO';
// import Upload from './components/Upload';
import MyDropzone from './components/MyDropzone';
import './styles/App.scss';
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 (
<div className="App">
<header className="App-header">
{[1,2,3,4,5,6,11,12,13,14].map(num => <img src={`/svgs/${num}.svg`} alt={`${num}.svg`} />)}
<div id='game'>
{elems.map(obj => <FontAwesomeIcon className={`${obj.field} r1`} style={{'--x': obj.x, '--y': obj.y} as CSSProperties} icon={faXmark} />)}
</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"
>
Battleships designed by macrovector
</a>
{/* <Gamefield/> */}
{/* <Homepage/> */}
{/* <Homepage2/> */}
{/* <SocketIO/> */}
{/* <Upload /> */}
<MyDropzone />
</header>
</div>
);

View 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

View 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;

View 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;

View 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

View 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;

View 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

View 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

View 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;

View 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

View 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;

View 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

View 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
View 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)
})
}

View 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;

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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;
}

View 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)));
}
}

View file

@ -0,0 +1 @@
$theme: #282c34;