merged react app into nextjs app
This commit is contained in:
parent
16b48b3ead
commit
c2770e5094
60 changed files with 612 additions and 25050 deletions
153
leaky-ships/components/Bluetooth.tsx
Normal file
153
leaky-ships/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
leaky-ships/components/BorderTiles.tsx
Normal file
54
leaky-ships/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
leaky-ships/components/FogImages.tsx
Normal file
9
leaky-ships/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
leaky-ships/components/Gamefield.tsx
Normal file
126
leaky-ships/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
leaky-ships/components/HitElems.tsx
Normal file
17
leaky-ships/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;
|
86
leaky-ships/components/Homepage.tsx
Normal file
86
leaky-ships/components/Homepage.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
function Homepage() {
|
||||
|
||||
const floorClient = (number: number) => Math.floor(number / 50)
|
||||
|
||||
const [columns, setColumns] = useState(0)
|
||||
const [rows, setRows] = useState(0)
|
||||
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
|
91
leaky-ships/components/Homepage2.tsx
Normal file
91
leaky-ships/components/Homepage2.tsx
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
function Homepage2() {
|
||||
|
||||
const floorClient = (number: number) => Math.floor(number / 50)
|
||||
|
||||
const [columns, setColumns] = useState(0)
|
||||
const [rows, setRows] = useState(0)
|
||||
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
leaky-ships/components/Labeling.tsx
Normal file
29
leaky-ships/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;
|
19
leaky-ships/components/Ships.tsx
Normal file
19
leaky-ships/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
leaky-ships/components/SocketIO.tsx
Normal file
30
leaky-ships/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
|
Loading…
Add table
Add a link
Reference in a new issue