Compare commits
50 commits
Author | SHA1 | Date | |
---|---|---|---|
87be301794 | |||
71eb36344c | |||
ef809ff548 | |||
a87b41d6c2 | |||
3d21464165 | |||
1c22ffb829 | |||
f5e1fe7e85 | |||
078a21c876 | |||
6cc8a4517c | |||
b2fd8f56fc | |||
a282e35119 | |||
b2c72ebb0d | |||
42e4af46e9 | |||
b7a321cff5 | |||
9243b04322 | |||
ebbbae4b15 | |||
71a43dfe5c | |||
e803832812 | |||
948a6deb4b | |||
e0d89cce6d | |||
f2fd6ca268 | |||
0cc0c69d7e | |||
65832b9b40 | |||
7c1d39950b | |||
c7862b8fe7 | |||
efd7a26932 | |||
da55e92ecd | |||
9fb1147158 | |||
bfd9d46892 | |||
cb66141806 | |||
84a15794e6 | |||
840ddab9bc | |||
3d0bf2bfde | |||
1e1003758d | |||
14bd854b91 | |||
e183af7dea | |||
7cf930ccf2 | |||
75cb6d537f | |||
65ca3d49ba | |||
2d6cd813f9 | |||
3652589944 | |||
039098d027 | |||
4e8de6d7e0 | |||
2ebaccfb2d | |||
83f187a7dc | |||
c837e0df2f | |||
038b9fe856 | |||
b949c46649 | |||
665889eea1 | |||
|
316847a192 |
66
.github/workflows/playwright.yml
vendored
|
@ -1,66 +0,0 @@
|
||||||
name: Playwright Tests
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main, master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main, master ]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
timeout-minutes: 60
|
|
||||||
runs-on: [self-hosted, linux]
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./leaky-ships
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Install Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v2
|
|
||||||
name: Install pnpm
|
|
||||||
with:
|
|
||||||
version: 8.7.4
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v3
|
|
||||||
name: Setup pnpm cache
|
|
||||||
with:
|
|
||||||
path: ${{ env.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('./leaky-ships/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-store-
|
|
||||||
|
|
||||||
- name: Add FA token
|
|
||||||
run: |
|
|
||||||
npm config set "@fortawesome:registry" "https://npm.fontawesome.com/"
|
|
||||||
npm config set "//npm.fontawesome.com/:_authToken" "${{ secrets.FONTAWESOME_NPM_AUTH_TOKEN }}"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
|
||||||
run: pnpm playwright install --with-deps
|
|
||||||
|
|
||||||
- name: 'Compiling page'
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.ENV_FILE }}" > .env
|
|
||||||
pnpm build
|
|
||||||
|
|
||||||
- name: Run Playwright tests
|
|
||||||
run: pnpm playwright test
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: playwright-report
|
|
||||||
path: leaky-ships/playwright-report/
|
|
||||||
retention-days: 30
|
|
|
@ -1,6 +1,5 @@
|
||||||
# leaky-ships
|
# leaky-ships
|
||||||
|
Battleship web app with react frontend and ASP.NET Core backend
|
||||||
Battleship web app made with SolidJS using solid-start.
|
|
||||||
|
|
||||||
## Bluetooth
|
## Bluetooth
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
{
|
{
|
||||||
"parser": "@typescript-eslint/parser",
|
"extends": "next/core-web-vitals"
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"plugins": ["solid"],
|
|
||||||
"extends": [
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:solid/typescript"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
55
leaky-ships/.gitignore
vendored
|
@ -1,35 +1,36 @@
|
||||||
/log
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
src/drizzle/migrations
|
|
||||||
|
|
||||||
dist
|
|
||||||
.vinxi
|
|
||||||
.output
|
|
||||||
.vercel
|
|
||||||
.netlify
|
|
||||||
netlify
|
|
||||||
|
|
||||||
# Environment
|
|
||||||
.env
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
/node_modules
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
# IDEs and editors
|
# testing
|
||||||
/.idea
|
/coverage
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
*.launch
|
|
||||||
.settings/
|
|
||||||
|
|
||||||
# Temp
|
# next.js
|
||||||
gitignore
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
# System Files
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
*.pem
|
||||||
|
|
||||||
# playwright
|
# debug
|
||||||
/test-results/
|
npm-debug.log*
|
||||||
/playwright-report/
|
yarn-debug.log*
|
||||||
/playwright/.cache/
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
.next
|
|
||||||
next.config.js
|
|
||||||
package.json
|
|
||||||
pnpm-lock.yaml
|
|
||||||
postcss.config.js
|
|
||||||
tailwind.config.js
|
|
175
leaky-ships/components/Bluetooth.tsx
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Bluetooth
|
52
leaky-ships/components/BorderTiles.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { CSSProperties, Dispatch, SetStateAction } from 'react';
|
||||||
|
import { borderCN, cornerCN, fieldIndex } from '../helpers';
|
||||||
|
import { LastLeftTileType, TargetPreviewPosType } from '../interfaces';
|
||||||
|
|
||||||
|
type TilesType = {
|
||||||
|
key: number,
|
||||||
|
isGameTile: boolean,
|
||||||
|
classNameString: string,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function BorderTiles({ props: { count, settingTarget, setTargetPreviewPos, setLastLeftTile } }: {
|
||||||
|
props: {
|
||||||
|
count: number,
|
||||||
|
settingTarget: (isGameTile: boolean, x: number, y: number) => void,
|
||||||
|
setTargetPreviewPos: Dispatch<SetStateAction<TargetPreviewPosType>>,
|
||||||
|
setLastLeftTile: Dispatch<SetStateAction<LastLeftTileType>>
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let tilesProperties: TilesType[] = [];
|
||||||
|
|
||||||
|
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={() => settingTarget(isGameTile, x, y)}
|
||||||
|
onMouseEnter={() => setTargetPreviewPos({ x, y, shouldShow: isGameTile })}
|
||||||
|
onMouseLeave={() => setLastLeftTile({ x, y })}
|
||||||
|
></div>
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BorderTiles;
|
11
leaky-ships/components/FogImages.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
function FogImages() {
|
||||||
|
return <>
|
||||||
|
<Image className='fog left' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<Image className='fog right' src={`/fog/fog2.png`} alt={`fog1.png`} />
|
||||||
|
<Image className='fog middle' src={`/fog/fog4.png`} alt={`fog4.png`} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FogImages;
|
47
leaky-ships/components/Gamefield.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { CSSProperties } 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 useGameEvent from './useGameEvent';
|
||||||
|
|
||||||
|
function Gamefield() {
|
||||||
|
|
||||||
|
const count = 12;
|
||||||
|
const {
|
||||||
|
targets,
|
||||||
|
eventBar,
|
||||||
|
setLastLeftTile,
|
||||||
|
settingTarget,
|
||||||
|
setTargetPreviewPos,
|
||||||
|
hits
|
||||||
|
} = useGameEvent(count);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='gamefield'>
|
||||||
|
{/* <Bluetooth /> */}
|
||||||
|
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
|
||||||
|
{/* Bordes */}
|
||||||
|
<BorderTiles props={{ count, settingTarget, setTargetPreviewPos, setLastLeftTile }} />
|
||||||
|
|
||||||
|
{/* Collumn lettes and row numbers */}
|
||||||
|
<Labeling count={count} />
|
||||||
|
|
||||||
|
{/* Ships */}
|
||||||
|
<Ships />
|
||||||
|
|
||||||
|
<HitElems hits={hits} />
|
||||||
|
|
||||||
|
{/* Fog images */}
|
||||||
|
{/* <FogImages /> */}
|
||||||
|
{targets}
|
||||||
|
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
||||||
|
</div>
|
||||||
|
{eventBar}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Gamefield
|
35
leaky-ships/components/GamefieldPointer.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { faCrosshairs } from '@fortawesome/pro-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { faRadar } from '@fortawesome/pro-thin-svg-icons';
|
||||||
|
|
||||||
|
function GamefieldPointer({ props: {
|
||||||
|
preview,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
show,
|
||||||
|
type,
|
||||||
|
edges,
|
||||||
|
imply
|
||||||
|
} }: {
|
||||||
|
props: {
|
||||||
|
preview: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
show: boolean,
|
||||||
|
type: string,
|
||||||
|
edges: string[],
|
||||||
|
imply: boolean,
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const isRadar = type === 'radar'
|
||||||
|
const style = !(isRadar && !edges.filter(s => s).length) ? { '--x': x, '--y': y } : { '--x1': x - 1, '--x2': x + 2, '--y1': y - 1, '--y2': y + 2 }
|
||||||
|
return (
|
||||||
|
<div className={classNames('hit-svg', { preview: preview }, 'target', type, { show: show }, ...edges, { imply: imply })} style={style as CSSProperties}>
|
||||||
|
<FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GamefieldPointer
|
87
leaky-ships/components/Grid.tsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
function Grid() {
|
||||||
|
|
||||||
|
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={classNames('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), (_tile, index) => createTile(index))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [params, position, active, count])
|
||||||
|
|
||||||
|
return createTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Grid
|
92
leaky-ships/components/Grid2.tsx
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { CSSProperties, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
function Grid2() {
|
||||||
|
|
||||||
|
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={classNames('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={classNames('headline', (!active ? 'active' : 'inactive'))}>{sentences[count % sentences.length]}</h1>
|
||||||
|
</div>
|
||||||
|
{Array.from(Array(params.quantity), (_tile, index) => createTile(index))}
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}, [params, position, active, action, count])
|
||||||
|
|
||||||
|
return createTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Grid2
|
17
leaky-ships/components/HitElems.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { faBurst, faXmark } from '@fortawesome/pro-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;
|
22
leaky-ships/components/Item.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import React, { CSSProperties } from 'react'
|
||||||
|
|
||||||
|
function Item({ props: { icon, text, amount, callback } }: {
|
||||||
|
props: {
|
||||||
|
icon: string,
|
||||||
|
text: string,
|
||||||
|
amount?: number,
|
||||||
|
callback: () => void
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className='item' onClick={callback}>
|
||||||
|
<div className={classNames('container', { amount: amount })} style={amount ? { '--amount': JSON.stringify(amount.toString()) } as CSSProperties : {}}>
|
||||||
|
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
|
||||||
|
</div>
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Item
|
30
leaky-ships/components/Labeling.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
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={classNames('label', orientation, field)} style={{'--x': x, '--y': y} as CSSProperties}>{field}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Labeling;
|
26
leaky-ships/components/Ships.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { CSSProperties } from 'react'
|
||||||
|
|
||||||
|
function Ships() {
|
||||||
|
let shipIndexes = [
|
||||||
|
{ size: 2, index: null },
|
||||||
|
{ size: 3, index: 1 },
|
||||||
|
{ size: 3, index: 2 },
|
||||||
|
{ size: 3, index: 3 },
|
||||||
|
{ size: 4, index: 1 },
|
||||||
|
{ size: 4, index: 2 }
|
||||||
|
];
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{shipIndexes.map(({ size, index }, i) => {
|
||||||
|
const filename = `/assets/ship_blue_${size}x${index ? '_' + index : ''}.gif`
|
||||||
|
return (
|
||||||
|
<div key={i} className={classNames('ship', 's' + size)} style={{ '--x': i + 3 } as CSSProperties}>
|
||||||
|
<img src={filename} alt={filename} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ships;
|
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
|
163
leaky-ships/components/useGameEvent.tsx
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
||||||
|
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialTargetPreviewPos, isHit } from '../helpers';
|
||||||
|
import { HitType, ItemsType, LastLeftTileType, ModeType, TargetPreviewPosType, TargetType } from '../interfaces';
|
||||||
|
import Item from './Item';
|
||||||
|
import GamefieldPointer from './GamefieldPointer';
|
||||||
|
|
||||||
|
function useGameEvent(count: number) {
|
||||||
|
const [lastLeftTile, setLastLeftTile] = useState<LastLeftTileType>(initlialLastLeftTile);
|
||||||
|
const [target, setTarget] = useState<TargetType>(initlialTarget);
|
||||||
|
const [eventReady, setEventReady] = useState(false);
|
||||||
|
const [appearOK, setAppearOK] = useState(false);
|
||||||
|
const [targetPreview, setTargetPreview] = useState<TargetType>(initlialTargetPreview);
|
||||||
|
const [targetPreviewPos, setTargetPreviewPos] = useState<TargetPreviewPosType>(initlialTargetPreviewPos);
|
||||||
|
const [hits, DispatchHits] = useReducer(hitReducer, [] as HitType[]);
|
||||||
|
// const [mode, setMode] = useState<[number, string]>([0, ''])
|
||||||
|
const [mode, setMode] = useState(0)
|
||||||
|
|
||||||
|
const modes = useMemo<ModeType[]>(() => [
|
||||||
|
{ pointerGrid: Array.from(Array(3), () => Array.from(Array(3))), type: 'radar' },
|
||||||
|
{ pointerGrid: Array.from(Array(3), () => Array.from(Array(1))), type: 'htorpedo' },
|
||||||
|
{ pointerGrid: Array.from(Array(1), () => Array.from(Array(3))), type: 'vhtorpedo' },
|
||||||
|
{ pointerGrid: [[{ x: 0, y: 0 }]], type: 'missle' }
|
||||||
|
], [])
|
||||||
|
|
||||||
|
const settingTarget = useCallback((isGameTile: boolean, x: number, y: number) => {
|
||||||
|
if (!isGameTile || isHit(hits, x, y).length)
|
||||||
|
return;
|
||||||
|
setTargetPreviewPos(e => ({ ...e, shouldShow: false }))
|
||||||
|
setTarget(t => {
|
||||||
|
if (t.x === x && t.y === y && t.show) {
|
||||||
|
DispatchHits({ type: 'fireMissle', payload: { hit: (x + y) % 2 !== 0, x, y } });
|
||||||
|
return { preview: false, show: false, x, y };
|
||||||
|
} else {
|
||||||
|
return { preview: false, show: true, x, y };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const targetList = useCallback((target: TargetType) => {
|
||||||
|
const { pointerGrid, type } = modes[mode]
|
||||||
|
const xLength = pointerGrid.length
|
||||||
|
const yLength = pointerGrid[0].length
|
||||||
|
const { x: targetX, y: targetY } = target
|
||||||
|
return pointerGrid.map((arr, i) => {
|
||||||
|
return arr.map((_, i2) => {
|
||||||
|
const relativeX = -Math.floor(xLength / 2) + i;
|
||||||
|
const relativeY = -Math.floor(yLength / 2) + i2;
|
||||||
|
const x = targetX + (relativeX ?? 0)
|
||||||
|
const y = targetY + (relativeY ?? 0)
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
type,
|
||||||
|
edges: [
|
||||||
|
i === 0 ? 'left' : '',
|
||||||
|
i === xLength - 1 ? 'right' : '',
|
||||||
|
i2 === 0 ? 'top' : '',
|
||||||
|
i2 === yLength - 1 ? 'bottom' : '',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.reduce((prev, curr) => [...prev, ...curr], [])
|
||||||
|
}, [mode, modes])
|
||||||
|
|
||||||
|
const isSet = useCallback((x: number, y: number) => !!targetList(target).filter(field => x === field.x && y === field.y).length && target.show, [target, targetList])
|
||||||
|
|
||||||
|
const composeTargetTiles = useCallback((target: TargetType) => {
|
||||||
|
const { preview, show } = target
|
||||||
|
const result = targetList(target).map(({ x, y, type, edges }) => {
|
||||||
|
return {
|
||||||
|
preview,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
show,
|
||||||
|
type,
|
||||||
|
edges,
|
||||||
|
imply: !!isHit(hits, x, y).length || (!!isSet(x, y) && preview),
|
||||||
|
// isborder: x < 2 || x > count + 1 || y < 2 || y > count + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// .filter(({ isborder }) => !isborder)
|
||||||
|
return result
|
||||||
|
}, [count, hits, isSet, targetList])
|
||||||
|
|
||||||
|
// if (!targetPreviewPos.shouldShow)
|
||||||
|
// return
|
||||||
|
|
||||||
|
// handle visibility and position change of targetPreview
|
||||||
|
useEffect(() => {
|
||||||
|
const { show, x, y } = targetPreview;
|
||||||
|
// if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid
|
||||||
|
const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
|
||||||
|
const isSet = x === target.x && y === target.y && target.show
|
||||||
|
|
||||||
|
if (show && !appearOK)
|
||||||
|
setTargetPreview(e => ({ ...e, show: false }));
|
||||||
|
if (!show && targetPreviewPos.shouldShow && eventReady && appearOK && !isHit(hits, x, y).length && !isSet && !hasLeft)
|
||||||
|
setTargetPreview(e => ({ ...e, show: true }));
|
||||||
|
}, [targetPreview, targetPreviewPos.shouldShow, hits, eventReady, appearOK, lastLeftTile, target])
|
||||||
|
|
||||||
|
// enable targetPreview event again after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
setEventReady(false);
|
||||||
|
if (targetPreview.show || !appearOK)
|
||||||
|
return;
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setTargetPreview(e => ({ ...e, x: targetPreviewPos.x, y: targetPreviewPos.y }));
|
||||||
|
setEventReady(true);
|
||||||
|
setAppearOK(true);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
// or abort if state has changed early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [appearOK, targetPreview.show, targetPreviewPos.x, targetPreviewPos.y]);
|
||||||
|
|
||||||
|
// approve targetPreview new position after 200 mil. sec.
|
||||||
|
useEffect(() => {
|
||||||
|
// early return to start cooldown only when about to show up
|
||||||
|
const autoTimeout = setTimeout(() => {
|
||||||
|
setAppearOK(!targetPreview.show)
|
||||||
|
}, targetPreview.show ? 500 : 300);
|
||||||
|
|
||||||
|
// or abort if movement is repeated early
|
||||||
|
return () => {
|
||||||
|
clearTimeout(autoTimeout);
|
||||||
|
}
|
||||||
|
}, [targetPreview.show]);
|
||||||
|
|
||||||
|
const targets = useMemo(() => [
|
||||||
|
...composeTargetTiles(target).map((props, i) => <GamefieldPointer key={'t' + i} props={props} />),
|
||||||
|
...composeTargetTiles(targetPreview).map((props, i) => <GamefieldPointer key={'p' + i} props={props} />)
|
||||||
|
], [composeTargetTiles, target, targetPreview])
|
||||||
|
|
||||||
|
const eventBar = useMemo(() => {
|
||||||
|
const items: ItemsType[] = [
|
||||||
|
{ icon: 'burger-menu', text: 'Menu' },
|
||||||
|
{ icon: 'radar', text: 'Radar scan', mode: 0, amount: 1 },
|
||||||
|
{ icon: 'missle', text: 'Fire torpedo', mode: 1, amount: 1 },
|
||||||
|
{ icon: 'scope', text: 'Fire missle', mode: 2 },
|
||||||
|
{ icon: 'gear', text: 'Settings' }
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div className='event-bar'>
|
||||||
|
{items.map((e, i) => (
|
||||||
|
<Item key={i} props={{ ...e, callback: () => { e.mode !== undefined ? setMode(e.mode) : {}; setTarget(e => ({ ...e, show: false })) } }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}, [])
|
||||||
|
return {
|
||||||
|
targets,
|
||||||
|
eventBar,
|
||||||
|
setLastLeftTile,
|
||||||
|
settingTarget,
|
||||||
|
setTargetPreviewPos,
|
||||||
|
hits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useGameEvent
|
27
leaky-ships/components/xstate/example.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { useMachine } from '@xstate/react';
|
||||||
|
import { createMachine } from 'xstate';
|
||||||
|
|
||||||
|
const toggleMachine = createMachine({
|
||||||
|
id: 'toggle',
|
||||||
|
initial: 'inactive',
|
||||||
|
states: {
|
||||||
|
inactive: {
|
||||||
|
on: { TOGGLE: 'active' }
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
on: { TOGGLE: 'inactive' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Toggler = () => {
|
||||||
|
const [state, send] = useMachine(toggleMachine);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button onClick={() => send('TOGGLE')}>
|
||||||
|
{state.value === 'inactive'
|
||||||
|
? 'Click to activate'
|
||||||
|
: 'Active! Click to deactivate'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
64
leaky-ships/components/xstate/test.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { useMachine } from '@xstate/react';
|
||||||
|
import { createMachine } from 'xstate';
|
||||||
|
|
||||||
|
const toggleMachine =
|
||||||
|
/** @xstate-layout N4IgpgJg5mDOIC5QBcD2UoBswDoCSAdgJbIDEAtgJ4AqArgE4EDaADALqKgAOqsJRqApxAAPRAFoALADYcATjkB2SQFYVclSwBMW9SoA0ISolWKcKgMzbpARkUtJADkdbpAXzeG0GbPmJkwAjAqOkZWDiQQHj5kASFIsQRxaRYcVUcLORtM6SdJCxtDYwQLRxt5FS1HB0UbFmlpTMkPL3QsXAB3AEMSADFUelCCUjARMABjWmQwIfDhaP5BYUSbOpwtCwLNepZtFjsixAt1ddctbIUHSpsWkG92nG6+gaGRggg5yIXYpYSTFVktWkujkGRYjkkkkOCFM5isrjsDmcrlu918T2Q-XoAFEglQRmNJtNZux5rxFvFQIldJIcAVMpoZHZ8lpFNDjnJTsDGRZFGpVKi2uiepiBrjgpQ3h9SV9yT9KaJ-oCbNzQVYIVCjEdWWljsi5Ls5JI+SoPJ4QARUBA4MI0WAyTE4ssJBtOY4UsdFDobOpJEpoSrHDhpNs5I1dpIbDoLIKfLhCCQHRTnUkbFkcO6WJ7vb7-VqYTZabU+ZsDSwsopYw8MVihkn5SnA8GVBlpHILDJTCHoX7yiwvdyIRlFLyq8Lnji8cVuHKnX8EDZqmkjQV++q+ZrirpZD79vtXWn1GPcLAABaoDq4iAAZXG9DAgXrc6piDUnOkjkUZUUI4Ucl07JLvYrJhg05zKLoZpuEAA */
|
||||||
|
createMachine({
|
||||||
|
predictableActionArguments: true,
|
||||||
|
id: 'toggle',
|
||||||
|
initial: "Init",
|
||||||
|
context: {
|
||||||
|
player: 1
|
||||||
|
},
|
||||||
|
states: {
|
||||||
|
Init: {
|
||||||
|
on: {
|
||||||
|
myTurn: "waitForTurn",
|
||||||
|
enemyTurn: "waitForEnemy",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
waitForTurn: {
|
||||||
|
entry: ["enableUI"],
|
||||||
|
exit: ["disableUI"],
|
||||||
|
on: {
|
||||||
|
executeTurn: { target: "waitForEnemy", actions: ["switchPlayers"] },
|
||||||
|
end: "showEndScreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
waitForEnemy: {
|
||||||
|
on: {
|
||||||
|
executeTurn: { target: "waitForTurn", actions: ["switchPlayers"] },
|
||||||
|
end: "showEndScreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showEndScreen: {
|
||||||
|
type: "final"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actions: {
|
||||||
|
switchPlayers: (context, event) => {
|
||||||
|
return context.player ? 0 : 1
|
||||||
|
},
|
||||||
|
enableUI: (context, event) => { },
|
||||||
|
disableUI: (context, event) => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const XStateTest = () => {
|
||||||
|
const [state, send] = useMachine(toggleMachine);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{JSON.stringify({
|
||||||
|
value: state.value,
|
||||||
|
context: state.context
|
||||||
|
}, null, 2)}
|
||||||
|
{state.nextEvents.map((event, i) => <div key={i}>
|
||||||
|
<button onClick={() => send(event)}>{event}</button>
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,11 +0,0 @@
|
||||||
import "dotenv/config"
|
|
||||||
import type { Config } from "drizzle-kit"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
schema: "./src/drizzle/schemas/Tables.ts",
|
|
||||||
out: "./src/drizzle/migrations",
|
|
||||||
driver: "pg",
|
|
||||||
dbCredentials: {
|
|
||||||
connectionString: process.env.DATABASE_URL ?? "",
|
|
||||||
},
|
|
||||||
} satisfies Config
|
|
|
@ -1,83 +0,0 @@
|
||||||
import { expect, test, type BrowserContext, type Page } from "@playwright/test"
|
|
||||||
|
|
||||||
let context: BrowserContext
|
|
||||||
let page: Page
|
|
||||||
|
|
||||||
test.describe.serial("Check Azure AD auth", () => {
|
|
||||||
test.beforeAll(async ({ browser }) => {
|
|
||||||
context = await browser.newContext()
|
|
||||||
page = await context.newPage()
|
|
||||||
page.route(/Regelwerk\.mp4/, (route) => route.abort())
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterAll(async () => {
|
|
||||||
await context.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Login process...", async () => {
|
|
||||||
await page.goto("/signin")
|
|
||||||
|
|
||||||
await page.locator("button#microsoft").click()
|
|
||||||
|
|
||||||
// Indicates email can be filled in
|
|
||||||
await page.waitForSelector("a#cantAccessAccount")
|
|
||||||
// Fill email input
|
|
||||||
await page.locator("input#i0116").fill(process.env.AUTH_EMAIL!)
|
|
||||||
|
|
||||||
// Click the "Next" button
|
|
||||||
await page.locator("input#idSIButton9").click()
|
|
||||||
|
|
||||||
// Indicates password can be filled in
|
|
||||||
await page.waitForSelector("a#idA_PWD_ForgotPassword")
|
|
||||||
// Fill password input
|
|
||||||
await page.locator("input#i0118").fill(process.env.AUTH_PW!)
|
|
||||||
|
|
||||||
// Click the "Sign in" button
|
|
||||||
await page.locator("input#idSIButton9").click()
|
|
||||||
|
|
||||||
// Click the "No" button
|
|
||||||
await page.locator("input#idBtn_Back").click()
|
|
||||||
|
|
||||||
await page.waitForSelector("#start")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Is logged in", async () => {
|
|
||||||
await page.goto("/signin")
|
|
||||||
await page.waitForSelector("button#signout")
|
|
||||||
await page.goto("/")
|
|
||||||
await page.waitForSelector("#start")
|
|
||||||
await page.evaluate(() => document.fonts.ready)
|
|
||||||
|
|
||||||
expect(await page.screenshot()).toMatchSnapshot("1.png", {
|
|
||||||
maxDiffPixelRatio: 0.02,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Is logged out", async () => {
|
|
||||||
await page.goto("/signout")
|
|
||||||
|
|
||||||
await page.locator("button#signout").click()
|
|
||||||
|
|
||||||
await page.waitForSelector("#start")
|
|
||||||
|
|
||||||
await page.goto(
|
|
||||||
"https://login.microsoftonline.com/common/oauth2/v2.0/logout",
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.waitForSelector("div#loginHeader")
|
|
||||||
|
|
||||||
const emailLocator = page.locator(
|
|
||||||
`[data-test-id="${process.env.AUTH_EMAIL ?? ""}"]`,
|
|
||||||
)
|
|
||||||
if (await emailLocator.isVisible()) {
|
|
||||||
await emailLocator.click()
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"The email locator is not present on the page. Skipping this step.",
|
|
||||||
)
|
|
||||||
// Optionally, you can throw an error, fail the test, or take any other desired action here.
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.waitForSelector("div#SignOutStatusMessage")
|
|
||||||
})
|
|
||||||
})
|
|
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 52 KiB |
|
@ -1,76 +0,0 @@
|
||||||
import {
|
|
||||||
test,
|
|
||||||
type Browser,
|
|
||||||
type BrowserContext,
|
|
||||||
type Page,
|
|
||||||
} from "@playwright/test"
|
|
||||||
import { createHash, randomBytes } from "crypto"
|
|
||||||
import { and, desc, eq } from "drizzle-orm"
|
|
||||||
import db from "~/drizzle"
|
|
||||||
import { verificationTokens } from "~/drizzle/schemas/Tables"
|
|
||||||
|
|
||||||
const player1Email = (browser: Browser) =>
|
|
||||||
browser.browserType().name() + "-player-1@example.com"
|
|
||||||
|
|
||||||
let context: BrowserContext
|
|
||||||
let page: Page
|
|
||||||
|
|
||||||
test.describe.serial("Check Email auth", () => {
|
|
||||||
test.beforeAll(async ({ browser }) => {
|
|
||||||
context = await browser.newContext()
|
|
||||||
page = await context.newPage()
|
|
||||||
})
|
|
||||||
|
|
||||||
test.afterAll(async () => {
|
|
||||||
await context.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Email login process...", async ({ browser }) => {
|
|
||||||
await page.goto("/signin")
|
|
||||||
|
|
||||||
await page.locator("input#email").fill(player1Email(browser))
|
|
||||||
await page.locator("button#email-submit").click()
|
|
||||||
|
|
||||||
await page.waitForURL("/api/auth/verify-request?provider=email&type=email")
|
|
||||||
|
|
||||||
const token = randomBytes(32).toString("hex")
|
|
||||||
|
|
||||||
const hash = createHash("sha256")
|
|
||||||
// Prefer provider specific secret, but use default secret if none specified
|
|
||||||
.update(`${token}${process.env.AUTH_SECRET}`)
|
|
||||||
.digest("hex")
|
|
||||||
|
|
||||||
// Use drizzle to fetch the latest token for the email
|
|
||||||
const latestToken = await db.query.verificationTokens.findFirst({
|
|
||||||
where: eq(verificationTokens.identifier, player1Email(browser)),
|
|
||||||
orderBy: [desc(verificationTokens.expires)],
|
|
||||||
})
|
|
||||||
await db
|
|
||||||
.update(verificationTokens)
|
|
||||||
.set({ token: hash })
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(verificationTokens.identifier, player1Email(browser)),
|
|
||||||
eq(verificationTokens.token, latestToken?.token ?? ""),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
callbackUrl: process.env.AUTH_URL!,
|
|
||||||
token,
|
|
||||||
email: player1Email(browser),
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.goto("/api/auth/callback/email?" + params)
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Verify Logged in...", async () => {
|
|
||||||
await page.goto("/signin")
|
|
||||||
await page.waitForSelector("button#signout")
|
|
||||||
})
|
|
||||||
|
|
||||||
test("Logging out...", async () => {
|
|
||||||
await page.locator("button#signout").click()
|
|
||||||
await page.waitForSelector("#start")
|
|
||||||
})
|
|
||||||
})
|
|
59
leaky-ships/helpers.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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 initlialLastLeftTile = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export const initlialTarget = {
|
||||||
|
preview: false,
|
||||||
|
show: false,
|
||||||
|
x: 2,
|
||||||
|
y: 2
|
||||||
|
};
|
||||||
|
export const initlialTargetPreview = {
|
||||||
|
preview: true,
|
||||||
|
show: false,
|
||||||
|
x: 2,
|
||||||
|
y: 2
|
||||||
|
};
|
||||||
|
export const initlialTargetPreviewPos = {
|
||||||
|
shouldShow: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
export const isHit = (hits: HitType[], x: number, y: number) => hits.filter(h => h.x === x && h.y === y);
|
61
leaky-ships/interfaces.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
export type LastLeftTileType = {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
export type TargetType = {
|
||||||
|
preview: boolean,
|
||||||
|
show: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
};
|
||||||
|
export type TargetPreviewPosType = {
|
||||||
|
shouldShow: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
export type TargetListType = {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
type: string,
|
||||||
|
edges: string[]
|
||||||
|
}
|
||||||
|
export type ModeType = {
|
||||||
|
pointerGrid: any[][],
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
export type ItemsType = {
|
||||||
|
icon: string,
|
||||||
|
text: string,
|
||||||
|
mode?: number,
|
||||||
|
amount?: number,
|
||||||
|
}
|
||||||
|
export type FieldType = {
|
||||||
|
field: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
export type HitType = {
|
||||||
|
hit: boolean,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
type fireMissle = {
|
||||||
|
type: 'fireMissle',
|
||||||
|
payload: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
hit: boolean
|
||||||
|
}
|
||||||
|
};
|
||||||
|
type removeMissle = {
|
||||||
|
type:
|
||||||
|
'removeMissle',
|
||||||
|
payload: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
hit: boolean
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HitDispatchType = fireMissle | removeMissle;
|
|
@ -1,5 +0,0 @@
|
||||||
server_pid=$(lsof -i :3000 -t)
|
|
||||||
if [[ -n $server_pid ]]; then
|
|
||||||
echo "Killing server..." $server_pid
|
|
||||||
kill -9 $server_pid
|
|
||||||
fi
|
|
6
leaky-ships/next.config.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
reactStrictMode: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
5969
leaky-ships/package-lock.json
generated
Normal file
|
@ -1,80 +1,39 @@
|
||||||
{
|
{
|
||||||
"name": "leaky-ships",
|
"name": "leaky-ships",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vinxi dev",
|
"dev": "next dev",
|
||||||
"build": "vinxi build",
|
"build": "next build",
|
||||||
"start": "vinxi start",
|
"start": "next start",
|
||||||
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"",
|
"lint": "next lint"
|
||||||
"push": "drizzle-kit push:pg",
|
|
||||||
"test": "pnpm playwright test --ui",
|
|
||||||
"typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
|
|
||||||
},
|
},
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "^0.27.0",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@auth/drizzle-adapter": "^0.7.0",
|
"@fortawesome/pro-duotone-svg-icons": "^6.2.1",
|
||||||
"@auth/solid-start": "^0.6.1",
|
"@fortawesome/pro-light-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
"@fortawesome/pro-regular-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/pro-duotone-svg-icons": "^6.5.1",
|
"@fortawesome/pro-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/pro-light-svg-icons": "^6.5.1",
|
"@fortawesome/pro-thin-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/pro-regular-svg-icons": "^6.5.1",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
|
"@fortawesome/sharp-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/pro-thin-svg-icons": "^6.5.1",
|
"@next/font": "13.1.1",
|
||||||
"@fortawesome/sharp-solid-svg-icons": "^6.5.1",
|
"@types/node": "18.11.18",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@types/react": "18.0.26",
|
||||||
"@solidjs/meta": "^0.29.3",
|
"@types/react-dom": "18.0.10",
|
||||||
"@solidjs/router": "^0.12.4",
|
"@xstate/react": "^3.0.2",
|
||||||
"@solidjs/start": "^0.5.9",
|
"classnames": "^2.3.2",
|
||||||
"classnames": "^2.5.1",
|
"eslint": "8.31.0",
|
||||||
"colors": "^1.4.0",
|
"eslint-config-next": "13.1.1",
|
||||||
"drizzle-orm": "^0.29.4",
|
"next": "13.1.1",
|
||||||
"drizzle-zod": "^0.5.1",
|
"react": "18.2.0",
|
||||||
"http-status": "^1.7.3",
|
"react-dom": "18.2.0",
|
||||||
"json-stable-stringify": "^1.1.1",
|
"socket.io-client": "^4.5.4",
|
||||||
"lodash-es": "^4.17.21",
|
"typescript": "4.9.4",
|
||||||
"nodemailer": "^6.9.10",
|
"xstate": "^4.35.2"
|
||||||
"object-hash": "^3.0.0",
|
|
||||||
"postgres": "^3.4.3",
|
|
||||||
"socket.io": "^4.7.4",
|
|
||||||
"socket.io-client": "^4.7.4",
|
|
||||||
"solid-color": "^0.0.4",
|
|
||||||
"solid-js": "^1.8.15",
|
|
||||||
"tinycolor2": "^1.6.0",
|
|
||||||
"unique-names-generator": "^4.7.1",
|
|
||||||
"vinxi": "^0.3.3",
|
|
||||||
"zod": "3.22.4"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.7.4",
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.41.2",
|
"@types/web-bluetooth": "^0.0.16",
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"sass": "^1.57.1"
|
||||||
"@types/json-stable-stringify": "^1.0.36",
|
|
||||||
"@types/node": "^20.11.19",
|
|
||||||
"@types/nodemailer": "^6.4.14",
|
|
||||||
"@types/object-hash": "^3.0.6",
|
|
||||||
"@types/web-bluetooth": "^0.0.20",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
|
||||||
"autoprefixer": "^10.4.17",
|
|
||||||
"dotenv": "^16.4.5",
|
|
||||||
"drizzle-kit": "^0.20.14",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-config-prettier": "^9.1.0",
|
|
||||||
"eslint-plugin-solid": "^0.13.1",
|
|
||||||
"pg": "^8.11.3",
|
|
||||||
"postcss": "^8.4.35",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"prettier-plugin-organize-imports": "^3.2.4",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
|
||||||
"sass": "^1.71.1",
|
|
||||||
"solid-start-node": "^0.3.10",
|
|
||||||
"tailwindcss": "^3.4.1",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"vite": "^5.1.4"
|
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"overrides": {
|
|
||||||
"@auth/core": "^0.13.0",
|
|
||||||
"solid-start": "^0.3.5"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
leaky-ships/pages/_app.tsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import '../styles/App.scss'
|
||||||
|
import '../styles/grid.scss'
|
||||||
|
import '../styles/grid2.scss'
|
||||||
|
import '../styles/homepage.scss'
|
||||||
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
13
leaky-ships/pages/_document.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
13
leaky-ships/pages/api/hello.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: 'John Doe' })
|
||||||
|
}
|
22
leaky-ships/pages/gamefield.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Gamefield from '../components/Gamefield'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main>
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<Gamefield />
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
22
leaky-ships/pages/grid.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Grid from '../components/Grid'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main>
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<Grid />
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
22
leaky-ships/pages/grid2.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Grid2 from '../components/Grid2'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main>
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<Grid2 />
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
32
leaky-ships/pages/homepage.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { faCompass } from '@fortawesome/pro-solid-svg-icons'
|
||||||
|
import { faCirclePlay } from '@fortawesome/pro-thin-svg-icons'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [heWantsToPlay, setHeWantsToPlay] = useState(false)
|
||||||
|
return (
|
||||||
|
<div id='box'>
|
||||||
|
<button id='navExpand'>
|
||||||
|
<FontAwesomeIcon icon={faCompass} />
|
||||||
|
</button>
|
||||||
|
<div className='beforeStartBox'>
|
||||||
|
<img id='shield' src="/assets/shield.png" alt="Shield Logo" />
|
||||||
|
{!heWantsToPlay ?
|
||||||
|
<>
|
||||||
|
<div id='videoWrapper'>
|
||||||
|
<FontAwesomeIcon icon={faCirclePlay} />
|
||||||
|
</div>
|
||||||
|
<button id='startButton' onClick={() => setHeWantsToPlay(true)}>START</button>
|
||||||
|
</> :
|
||||||
|
<div id='startBox'>
|
||||||
|
<div>
|
||||||
|
<button className='optionButton'>Raum erstellen</button>
|
||||||
|
<button className='optionButton'>Raum beitreten</button>
|
||||||
|
<button className='optionButton'>Zuschauen</button>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
24
leaky-ships/pages/index.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { XStateTest } from '../components/xstate/test'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main>
|
||||||
|
<p><Link href='/gamefield' target='_blank'>Gamefield</Link></p>
|
||||||
|
<p><Link href='/homepage' target='_blank'>Homepage</Link></p>
|
||||||
|
<p><Link href='/grid' target='_blank'>Grid Effect</Link></p>
|
||||||
|
<p><Link href='/grid2' target='_blank'>Grid Effect with Content</Link></p>
|
||||||
|
<p><Link href='/socketio' target='_blank'>SocketIO</Link></p>
|
||||||
|
<XStateTest />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
22
leaky-ships/pages/socketio.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import SocketIO from '../components/SocketIO'
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create Next App</title>
|
||||||
|
<meta name="description" content="Generated by create next app" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<main>
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<SocketIO />
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,78 +0,0 @@
|
||||||
import { defineConfig, devices } from "@playwright/test"
|
|
||||||
import dotenv from "dotenv"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read environment variables from file.
|
|
||||||
* https://github.com/motdotla/dotenv
|
|
||||||
*/
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: "./e2e",
|
|
||||||
/* Run tests in files in parallel */
|
|
||||||
fullyParallel: true,
|
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
/* Retry on CI only */
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
|
||||||
workers: process.env.CI ? 1 : undefined,
|
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
||||||
reporter: "html",
|
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
||||||
use: {
|
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
|
||||||
baseURL: process.env.AUTH_URL!,
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
||||||
trace: "on-first-retry",
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: "chromium",
|
|
||||||
use: { ...devices["Desktop Chrome"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "firefox",
|
|
||||||
use: { ...devices["Desktop Firefox"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "webkit",
|
|
||||||
use: { ...devices["Desktop Safari"] },
|
|
||||||
},
|
|
||||||
|
|
||||||
/* Test against mobile viewports. */
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Chrome',
|
|
||||||
// use: { ...devices['Pixel 5'] },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Mobile Safari',
|
|
||||||
// use: { ...devices['iPhone 12'] },
|
|
||||||
// },
|
|
||||||
|
|
||||||
/* Test against branded browsers. */
|
|
||||||
// {
|
|
||||||
// name: 'Microsoft Edge',
|
|
||||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: 'Google Chrome',
|
|
||||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
|
||||||
// },
|
|
||||||
],
|
|
||||||
|
|
||||||
/* Run your local dev server before starting the tests */
|
|
||||||
webServer: {
|
|
||||||
command: "pnpm start",
|
|
||||||
url: process.env.AUTH_URL,
|
|
||||||
reuseExistingServer: !process.env.CI,
|
|
||||||
},
|
|
||||||
})
|
|
8316
leaky-ships/pnpm-lock.yaml
generated
|
@ -1,6 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
semi: false,
|
|
||||||
plugins: [
|
|
||||||
"prettier-plugin-organize-imports",
|
|
||||||
"prettier-plugin-tailwindcss", // MUST come last
|
|
||||||
],
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
generator zod {
|
|
||||||
provider = "zod-prisma-types"
|
|
||||||
}
|
|
||||||
|
|
||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
}
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "postgres"
|
|
||||||
url = env("DATABASE_URL")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Account {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
userId String @map("user_id")
|
|
||||||
type String
|
|
||||||
provider String
|
|
||||||
providerAccountId String @map("provider_account_id")
|
|
||||||
refresh_token String?
|
|
||||||
access_token String?
|
|
||||||
expires_at Int?
|
|
||||||
ext_expires_in Int?
|
|
||||||
token_type String?
|
|
||||||
scope String?
|
|
||||||
id_token String?
|
|
||||||
session_state String?
|
|
||||||
oauth_token_secret String?
|
|
||||||
oauth_token String?
|
|
||||||
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@unique([provider, providerAccountId])
|
|
||||||
@@map("accounts")
|
|
||||||
}
|
|
||||||
|
|
||||||
model Session {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
sessionToken String @unique @map("session_token")
|
|
||||||
userId String @map("user_id")
|
|
||||||
expires DateTime
|
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
||||||
|
|
||||||
@@map("sessions")
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
name String?
|
|
||||||
email String? @unique
|
|
||||||
emailVerified DateTime? @map("email_verified")
|
|
||||||
image String?
|
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
|
||||||
games User_Game[]
|
|
||||||
accounts Account[]
|
|
||||||
sessions Session[]
|
|
||||||
|
|
||||||
@@map("users")
|
|
||||||
}
|
|
||||||
|
|
||||||
model VerificationToken {
|
|
||||||
identifier String
|
|
||||||
token String @unique
|
|
||||||
expires DateTime
|
|
||||||
|
|
||||||
@@unique([identifier, token])
|
|
||||||
@@map("verificationtokens")
|
|
||||||
}
|
|
||||||
|
|
||||||
enum GameState {
|
|
||||||
lobby
|
|
||||||
starting
|
|
||||||
running
|
|
||||||
ended
|
|
||||||
aborted
|
|
||||||
}
|
|
||||||
|
|
||||||
model Game {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
updatedAt DateTime @updatedAt
|
|
||||||
state GameState @default(lobby)
|
|
||||||
allowSpectators Boolean @default(true)
|
|
||||||
allowSpecials Boolean @default(true)
|
|
||||||
allowChat Boolean @default(true)
|
|
||||||
allowMarkDraw Boolean @default(true)
|
|
||||||
gamePin Gamepin?
|
|
||||||
users User_Game[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Gamepin {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
pin String @unique
|
|
||||||
gameId String @unique
|
|
||||||
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Orientation {
|
|
||||||
h
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
model Ship {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
size Int
|
|
||||||
variant Int
|
|
||||||
x Int
|
|
||||||
y Int
|
|
||||||
orientation Orientation
|
|
||||||
user_GameId String
|
|
||||||
User_Game User_Game @relation(fields: [user_GameId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
model Hit {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
x Int
|
|
||||||
y Int
|
|
||||||
hit Boolean
|
|
||||||
user_GameId String
|
|
||||||
User_Game User_Game @relation(fields: [user_GameId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
model User_Game {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
gameId String
|
|
||||||
userId String
|
|
||||||
index Int
|
|
||||||
moves Move[]
|
|
||||||
ships Ship[]
|
|
||||||
hits Hit[]
|
|
||||||
chats Chat[]
|
|
||||||
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
||||||
user User @relation(fields: [userId], references: [id])
|
|
||||||
|
|
||||||
@@unique([gameId, index])
|
|
||||||
@@unique([gameId, userId])
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MoveType {
|
|
||||||
missile
|
|
||||||
vtorpedo
|
|
||||||
htorpedo
|
|
||||||
radar
|
|
||||||
}
|
|
||||||
|
|
||||||
model Move {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
index Int
|
|
||||||
type MoveType
|
|
||||||
x Int
|
|
||||||
y Int
|
|
||||||
orientation Orientation
|
|
||||||
user_game_id String
|
|
||||||
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
||||||
|
|
||||||
model Chat {
|
|
||||||
id String @id @default(cuid())
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
message String?
|
|
||||||
event String?
|
|
||||||
user_game_id String
|
|
||||||
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 5.8 KiB |
BIN
leaky-ships/public/assets/shield.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 4.7 KiB |
|
@ -1,176 +0,0 @@
|
||||||
|
|
||||||
https://cute-freefont.flop.jp/tori_checkpoint.html
|
|
||||||
|
|
||||||
<EFBFBD>`<60>F<EFBFBD>b<EFBFBD>N<EFBFBD>|<7C>C<EFBFBD><43><EFBFBD>g<EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g
|
|
||||||
|
|
||||||
Version 2.076
|
|
||||||
|
|
||||||
OpenType Edition
|
|
||||||
|
|
||||||
|
|
||||||
readme
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>̓x<EFBFBD>́A<EFBFBD>`<60>F<EFBFBD>b<EFBFBD>N<EFBFBD>|<7C>C<EFBFBD><43><EFBFBD>g<EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD><67><EFBFBD>_<EFBFBD>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>[<5B>h<EFBFBD><68><EFBFBD>Ă<EFBFBD><C482><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>肪<EFBFBD>Ƃ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1. <20>`<60>F<EFBFBD>b<EFBFBD>N<EFBFBD>|<7C>C<EFBFBD><43><EFBFBD>g<EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD>Ƃ<EFBFBD>
|
|
||||||
<EFBFBD>`<60>F<EFBFBD>b<EFBFBD>N<EFBFBD>|<7C>C<EFBFBD><43><EFBFBD>g<EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD>̓t<CD83><74><EFBFBD>[<5B>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD>ł<EFBFBD><C582>B
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>ē<EFBFBD><EFBFBD>{<7B>e<EFBFBD><65><EFBFBD>r<EFBFBD>n<EFBFBD><6E>ŕ<EFBFBD><C595><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ă<EFBFBD><C482><EFBFBD><EFBFBD>A<EFBFBD><41><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̃N<CC83>C<EFBFBD>Y<EFBFBD>ԑg
|
|
||||||
<EFBFBD>w<EFBFBD>A<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>J<EFBFBD><EFBFBD><EFBFBD>f<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD>g<EFBFBD><EFBFBD><EFBFBD>N<EFBFBD>C<EFBFBD>Y<EFBFBD>x<EFBFBD><EFBFBD><EFBFBD>C<EFBFBD><EFBFBD><EFBFBD>[<5B>W<EFBFBD><57><EFBFBD><EFBFBD><EFBFBD>a<EFBFBD><61><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̂Ƃ<CC82><C682>āA
|
|
||||||
|
|
||||||
<EFBFBD>E<EFBFBD>Ђ炪<EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD>J<EFBFBD>^<5E>J<EFBFBD>i<EFBFBD>i<EFBFBD>^<5E>C<EFBFBD>g<EFBFBD><67><EFBFBD><EFBFBD><EFBFBD>S<EFBFBD>u<EFBFBD>E<EFBFBD><45><EFBFBD>g<EFBFBD><67><EFBFBD>N<EFBFBD>C<EFBFBD>Y<EFBFBD>v<EFBFBD><76><EFBFBD>j
|
|
||||||
<EFBFBD>E<EFBFBD>p<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD>L<EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>iJIS<EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><F190858F>ꎮ<EFBFBD>AIBM<42>g<EFBFBD><67><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꎮ<EFBFBD>{<7B><><EFBFBD>j
|
|
||||||
|
|
||||||
<EFBFBD>ȏ<EFBFBD>̗v<EFBFBD>f<EFBFBD><EFBFBD><EFBFBD>J<EFBFBD>o<EFBFBD>[<5B><><EFBFBD>Ă<EFBFBD><C482><EFBFBD>܂<EFBFBD><DC82>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2. <20><><EFBFBD>^<5E><><EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD>Ђ炪<EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD>J<EFBFBD>^<5E>J<EFBFBD>i<EFBFBD>c<EFBFBD>S<EFBFBD>p/<2F><><EFBFBD>p<EFBFBD>i<EFBFBD>^<5E>C<EFBFBD>g<EFBFBD><67><EFBFBD><EFBFBD><EFBFBD>S<EFBFBD>u<EFBFBD>E<EFBFBD><45><EFBFBD>g<EFBFBD><67><EFBFBD>N<EFBFBD>C<EFBFBD>Y<EFBFBD>v<EFBFBD><76><EFBFBD>j
|
|
||||||
<EFBFBD>E<EFBFBD>A<EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>@<40>x<EFBFBD>b<EFBFBD>g<EFBFBD>c<EFBFBD>S<EFBFBD>p/<2F><><EFBFBD>p
|
|
||||||
<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>c<EFBFBD>S<EFBFBD>p/<2F><><EFBFBD>p
|
|
||||||
<EFBFBD>E<EFBFBD>L<EFBFBD><EFBFBD><EFBFBD>c<EFBFBD>M<EFBFBD><EFBFBD><EFBFBD>V<EFBFBD>A<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>A<EFBFBD>L<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>A<EFBFBD><EFBFBD><EFBFBD>̑<EFBFBD><EFBFBD>ꎮ<EFBFBD>ԗ<EFBFBD><EFBFBD>B<EFBFBD>A<EFBFBD>N<EFBFBD>T<EFBFBD><EFBFBD><EFBFBD>ɂ<EFBFBD><EFBFBD>Ή<EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>cJIS<EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD>6,355<35><35><EFBFBD>{IBM<42>g<EFBFBD><67><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>{<7B><><EFBFBD>{<7B><><EFBFBD><EFBFBD><EFBFBD>\<5C>͌<EFBFBD><CD8C><EFBFBD>1<EFBFBD><31><EFBFBD>z<EFBFBD><7A><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>i<EFBFBD>ꕔ<EFBFBD><EA9594><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>j<EFBFBD>{<7B><><EFBFBD>@<40>v<EFBFBD>F<EFBFBD><46>6,900<30><30>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3. <20><><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>݂̂Ƃ<EFBFBD><EFBFBD><EFBFBD>A
|
|
||||||
Microsoft Windows XP<58>AVista<74>A7<41>A8<41>A10<31>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Mac OS X<>œ<EFBFBD><C593><EFBFBD><EFBFBD><EFBFBD>m<EFBFBD>F<EFBFBD><46><EFBFBD>Ă<EFBFBD><C482><EFBFBD>܂<EFBFBD><DC82>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
4. <20><><EFBFBD>g<EFBFBD>p<EFBFBD>E<EFBFBD>z<EFBFBD>z<EFBFBD>ɂ<C982><C282><EFBFBD>
|
|
||||||
<EFBFBD>E<EFBFBD>F<EFBFBD>u<EFBFBD>T<EFBFBD>C<EFBFBD>g<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>A<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ȃǂł̎g<EFBFBD>p<EFBFBD>̂ق<EFBFBD><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>p<EFBFBD><EFBFBD><EFBFBD>p<EFBFBD>i<EFBFBD><EFBFBD><EFBFBD>l<EFBFBD><EFBFBD><EFBFBD>Ȃǂł̎g<EFBFBD>p<EFBFBD>j<EFBFBD>ɂ<EFBFBD><EFBFBD>Ă<EFBFBD><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>ɐ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>݂͐<EFBFBD><EFBFBD>Ă<EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD><EFBFBD>B
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD>ɂ<EFBFBD><EFBFBD>g<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ΐ<EFBFBD><EFBFBD><EFBFBD>Җ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɐs<EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
<EFBFBD>g<EFBFBD>p<EFBFBD><EFBFBD><EFBFBD>A<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>K<EFBFBD>v<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD>낵<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ύ<EFBFBD><EFBFBD><EFBFBD>ł<EFBFBD><EFBFBD><EFBFBD><EFBFBD>\<5C>ł<EFBFBD><C582>̂ŁA
|
|
||||||
web<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Twitter<EFBFBD>Ȃǂł<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>肪<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ł<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
<EFBFBD>Ȃ<EFBFBD><EFBFBD>A<EFBFBD><EFBFBD><EFBFBD>`<60><><EFBFBD>邢<EFBFBD>͒<EFBFBD><CD92>쌠<EFBFBD>\<5C><><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͂<EFBFBD><EFBFBD>̗<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ς<EFBFBD><EFBFBD>Ă̓z<EFBFBD>z<EFBFBD>͂<EFBFBD><EFBFBD>e<EFBFBD>͂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5. <20>Ɛ<EFBFBD>
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>H<EFBFBD><EFBFBD><EFBFBD>g<EFBFBD>̎g<EFBFBD>p<EFBFBD>ɂ<EFBFBD>薜<EFBFBD><EFBFBD><EFBFBD>ꐶ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Q<EFBFBD>ɑ<EFBFBD><EFBFBD>A
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>͈<EFBFBD>̐ӔC<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˂܂<EFBFBD><EFBFBD>B<EFBFBD><EFBFBD><EFBFBD>炩<EFBFBD><EFBFBD><EFBFBD>߂<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
6. <20><><EFBFBD>̑<EFBFBD>
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>t<EFBFBD>H<EFBFBD><EFBFBD><EFBFBD>g<EFBFBD>́A<EFBFBD>w<EFBFBD>A<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>J<EFBFBD><EFBFBD><EFBFBD>f<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD>g<EFBFBD><EFBFBD><EFBFBD>N<EFBFBD>C<EFBFBD>Y<EFBFBD>x<EFBFBD>̓n<EFBFBD>앨<EFBFBD>ł<EFBFBD><EFBFBD>B
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>쌠<EFBFBD>́A<EFBFBD><EFBFBD><EFBFBD>ꂼ<EFBFBD><EFBFBD>̌<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>L<EFBFBD>҂ɋA<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><EFBFBD>B
|
|
||||||
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>^<5E><><EFBFBD><EFBFBD>Ă<EFBFBD><C482>镶<EFBFBD><E995B6><EFBFBD>E<EFBFBD>L<EFBFBD><4C><EFBFBD>̎<EFBFBD>ނ<EFBFBD><DE82><EFBFBD>ю<EFBFBD><D18E>`<60>A<EFBFBD><41><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>A<EFBFBD>ϊ<EFBFBD><CF8A><EFBFBD><EFBFBD>蓖<EFBFBD>āA
|
|
||||||
<EFBFBD>܂<EFBFBD><EFBFBD>_<EFBFBD>E<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>[<5B>h<EFBFBD>p<EFBFBD>̃<EFBFBD><CC83><EFBFBD><EFBFBD>N<EFBFBD>Ȃǂ͗\<5C><><EFBFBD>Ȃ<EFBFBD><C882>ύX<CF8D><58><EFBFBD><EFBFBD>ꍇ<EFBFBD><EA8D87><EFBFBD><EFBFBD><EFBFBD><EFBFBD>܂<EFBFBD><DC82>B
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
7. <20>X<EFBFBD>V<EFBFBD><56><EFBFBD><EFBFBD>
|
|
||||||
2011/04/01 Version 1.00 <09>E<EFBFBD><45><EFBFBD>J
|
|
||||||
2011/04/07 Version 1.01 <09>E<EFBFBD><45><EFBFBD><EFBFBD>103<30><33><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/04/14 Version 1.02 <09>E<EFBFBD><45><EFBFBD><EFBFBD>111<31><31><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/04/21 Version 1.03 <09>E<EFBFBD><45><EFBFBD><EFBFBD>131<33><31><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/04/28 Version 1.04 <09>E<EFBFBD><45><EFBFBD><EFBFBD>133<33><33><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/05/05 Version 1.05 <09>E<EFBFBD><45><EFBFBD><EFBFBD>144<34><34><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/05/12 Version 1.10 <09>E<EFBFBD><45><EFBFBD><EFBFBD>128<32><38><EFBFBD><EFBFBD>lj<EFBFBD><C789>B
|
|
||||||
<09>@<40><>p<EFBFBD><70><EFBFBD><EFBFBD>2,136<33><36><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>S<EFBFBD><53><EFBFBD>^<5E><><EFBFBD>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD>p<EFBFBD><70><EFBFBD><EFBFBD><EFBFBD>̕<EFBFBD><CC95><EFBFBD><EFBFBD>Ԋu<D48A><75>
|
|
||||||
<09>@<40>A<EFBFBD><41><EFBFBD>t<EFBFBD>@<40>x<EFBFBD>b<EFBFBD>g<EFBFBD>ɓ<EFBFBD><C993><EFBFBD>
|
|
||||||
<09>EOpenType<70>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD><EFBFBD>
|
|
||||||
2011/05/12 Version 1.101 <09>E1<45><31><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>i<EFBFBD>u<EFBFBD><75><EFBFBD>v<EFBFBD>̍Ō<CC8D>̈<EFBFBD>悪
|
|
||||||
<09>@<40><><EFBFBD><EFBFBD><EFBFBD>Ă<EFBFBD><C482><EFBFBD><EFBFBD>̂𐳂<CC82><F090B382><EFBFBD><EFBFBD>j
|
|
||||||
2011/05/13 Version 1.102 <09>E1<45><31><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>i<EFBFBD>u<EFBFBD><75><EFBFBD>v<EFBFBD>Ɂu<C981><75>v<EFBFBD><76>
|
|
||||||
<09>@<40><><EFBFBD>蓖<EFBFBD>Ă<EFBFBD><C482>Ă<EFBFBD><C482><EFBFBD><EFBFBD>̂𐳂<CC82><F090B382><EFBFBD><EFBFBD>j
|
|
||||||
2011/05/19 Version 1.11 <09>E<EFBFBD><45><EFBFBD><EFBFBD>120<32><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/05/26 Version 1.12 <09>E<EFBFBD><45><EFBFBD><EFBFBD>120<32><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/06/02 Version 1.13 <09>E<EFBFBD><45><EFBFBD><EFBFBD>120<32><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/06/09 Version 1.14 <09>E<EFBFBD><45><EFBFBD><EFBFBD>120<32><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/06/16 Version 1.15 <09>E<EFBFBD><45><EFBFBD><EFBFBD>120<32><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/06/23 Version 1.20 <09>E<EFBFBD><45><EFBFBD><EFBFBD>150<35><30><EFBFBD><EFBFBD>lj<EFBFBD><C789>B
|
|
||||||
<09>@JIS<49><53>ꐅ<EFBFBD><EA9085><EFBFBD>ɑ<EFBFBD><C991><EFBFBD><EFBFBD>銿<EFBFBD><E98ABF>2,965<36><35><EFBFBD>A
|
|
||||||
<09>@<40>l<EFBFBD><6C><EFBFBD>p<EFBFBD><70><EFBFBD><EFBFBD><EFBFBD>i<EFBFBD><69>ّ̎<D991><CC8E>̂݁j<DD81><6A>
|
|
||||||
<09>@<40><><EFBFBD>S<EFBFBD><53><EFBFBD>^<5E><><EFBFBD>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
2011/07/07 Version 1.21 <09>E<EFBFBD><45><EFBFBD><EFBFBD>555<35><35><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/07/14 Version 1.22 <09>E<EFBFBD><45><EFBFBD><EFBFBD>25<32><35><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E3<45><33><EFBFBD>i<EFBFBD>^<5E>֑A<D691>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2011/08/18 Version 1.23 <09>E<EFBFBD><45><EFBFBD><EFBFBD>30<33><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E3<45><33><EFBFBD>i<EFBFBD><69><EFBFBD><EFBFBD>Ⴡj<E18381><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2011/09/01 Version 1.24 <09>E<EFBFBD><45><EFBFBD><EFBFBD>49<34><39><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E1<45><31><EFBFBD>i<EFBFBD>n<EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>E<EFBFBD>e<EFBFBD><65>L<EFBFBD><4C><EFBFBD>̕<EFBFBD><CC95><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>C<EFBFBD><43>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD>̑<EFBFBD><CC91>A<EFBFBD><41><EFBFBD>`<60><><EFBFBD>C<EFBFBD><43><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2011/09/12 Version 1.25 <09>E<EFBFBD><45><EFBFBD><EFBFBD>167<36><37><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2011/10/06 Version 1.30 <09>E<EFBFBD><45><EFBFBD><EFBFBD>130<33><30><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E<EFBFBD>t<EFBFBD><74><EFBFBD><EFBFBD><EFBFBD>X<EFBFBD><58>̃A<CC83>N<EFBFBD>T<EFBFBD><54><EFBFBD>ɑ<EFBFBD>\<5C><><EFBFBD><EFBFBD><EFBFBD>A
|
|
||||||
<09>@<40>_<EFBFBD>C<EFBFBD>A<EFBFBD>N<EFBFBD><4E><EFBFBD>e<EFBFBD>B<EFBFBD>J<EFBFBD><4A><EFBFBD>}<7D>[<5B>N<EFBFBD>t<EFBFBD><74><EFBFBD><EFBFBD>
|
|
||||||
<09>@<40><><EFBFBD>e<EFBFBD><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>i<EFBFBD>ꕔ<EFBFBD>j<EFBFBD><6A>lj<EFBFBD>
|
|
||||||
<09>EUnicode<64>`<60><><EFBFBD>L<EFBFBD><4C><EFBFBD><EFBFBD><EFBFBD>e<EFBFBD><65>lj<EFBFBD>
|
|
||||||
2012/01/01 Version 1.40 <09>E<EFBFBD><45><EFBFBD><EFBFBD>1,018<31><38><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E1<45><31><EFBFBD>i<EFBFBD>M<EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2012/04/01 Version 2.00 <09>E<EFBFBD><45><EFBFBD><EFBFBD>1,814<31><34><EFBFBD><EFBFBD>lj<EFBFBD><C789>B
|
|
||||||
<09>@JIS<49><53><EFBFBD>/<2F><><EFBFBD><F190858F>ɑ<EFBFBD><C991><EFBFBD><EFBFBD>銿<EFBFBD><E98ABF>6,355<35><35><EFBFBD>A
|
|
||||||
<09>@IBM<42>g<EFBFBD><67><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɑ<EFBFBD><C991><EFBFBD><EFBFBD>銿<EFBFBD><E98ABF>360<36><30><EFBFBD><EFBFBD>
|
|
||||||
<09>@<40><><EFBFBD>S<EFBFBD><53><EFBFBD>^<5E><><EFBFBD>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
<09>E2<45><32><EFBFBD>i<EFBFBD><69>⤁j<E2A481><6A><EFBFBD><EFBFBD><EFBFBD><EFBFBD>B
|
|
||||||
<09>@<40>u<EFBFBD><75><EFBFBD>v<EFBFBD>͐V<CD90><56><EFBFBD>̂ɕ킢<C995>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
<09>E<EFBFBD>L<EFBFBD><4C><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD>̑<EFBFBD><CC91>A<EFBFBD><41><EFBFBD>`<60><><EFBFBD>C<EFBFBD><43><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2012/04/26 Version 2.001 <09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2012/05/12 Version 2.002 <09>E9<45><39><EFBFBD>i<EFBFBD>n<EFBFBD><6E><EFBFBD><EFBFBD><EFBFBD>Ɲ<EFBFBD><C69D><EFBFBD><EFBFBD>y<EFBFBD>B<EFBFBD><42><EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2012/05/20 Version 2.003 <09>E6<45><36><EFBFBD>i<EFBFBD><69><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>|<7C>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2012/09/09 Version 2.004 <09>E<EFBFBD>r<EFBFBD><72><EFBFBD>f<EFBFBD>Ђ<EFBFBD><D082>C<EFBFBD><43><EFBFBD>B<EFBFBD><42><EFBFBD><EFBFBD><EFBFBD>ׂ<EFBFBD><D782><EFBFBD><EFBFBD>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
2012/10/04 Version 2.005 <09>E<EFBFBD>A<EFBFBD><41><EFBFBD>t<EFBFBD>@<40>x<EFBFBD>b<EFBFBD>g<EFBFBD>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>E<EFBFBD>L<EFBFBD><4C><EFBFBD>̎<EFBFBD><CC8E>`<60><>
|
|
||||||
<09>@<40>ꕔ<EFBFBD>C<EFBFBD><43><EFBFBD>B
|
|
||||||
<09>@<40><><EFBFBD><EFBFBD><EFBFBD>̓N<CD83>C<EFBFBD>Y<EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD>uU-Fo<46>v<EFBFBD><76><EFBFBD><EFBFBD>
|
|
||||||
<09>@<40>t<EFBFBD>ڐA<DA90><41><EFBFBD>܂<EFBFBD><DC82><EFBFBD>
|
|
||||||
2012/10/11 Version 2.006 <09>E<EFBFBD>_<EFBFBD>C<EFBFBD>A<EFBFBD>N<EFBFBD><4E><EFBFBD>e<EFBFBD>B<EFBFBD>J<EFBFBD><4A><EFBFBD>}<7D>[<5B>N<EFBFBD>t<EFBFBD><74>
|
|
||||||
<09>@<40><><EFBFBD>e<EFBFBD><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2012/10/21 Version 2.01 <09>E<EFBFBD><45><EFBFBD><EFBFBD>2<EFBFBD><32><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E7<45><37><EFBFBD>i<EFBFBD><69><EFBFBD><EFBFBD><EFBFBD><EFBFBD>S<EFBFBD><53><EFBFBD><EFBFBD><EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2013/05/06 Version 2.02 <09>E<EFBFBD>L<EFBFBD><4C>20<32><30>ނ<EFBFBD>lj<EFBFBD>
|
|
||||||
2013/09/12 Version 2.03 <09>E<EFBFBD>Ђ炪<D082>ȁE<C881>J<EFBFBD>^<5E>J<EFBFBD>i<EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2013/12/01 Version 2.031 <09>E4<45><34><EFBFBD>i<EFBFBD><69><EFBFBD>ȁA<C881>c<EFBFBD><63><EFBFBD><EFBFBD><EFBFBD>p<EFBFBD>̇<EFBFBD><CC87><EFBFBD><EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>E2<45><32><EFBFBD>i?<3F>H<EFBFBD>j<EFBFBD><6A><EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/03/03 Version 2.04 <09>E<EFBFBD>g<EFBFBD><67><EFBFBD>t<EFBFBD>H<EFBFBD><48><EFBFBD>g<EFBFBD>ɐ<EFBFBD>s<EFBFBD><73><EFBFBD>^<5E><><EFBFBD>Ă<EFBFBD><C482><EFBFBD><EFBFBD>L<EFBFBD><4C><EFBFBD><EFBFBD>
|
|
||||||
<09>@20<32><30>ޒlj<DE92>
|
|
||||||
<09>E6<45><36><EFBFBD>i*<2A><><EFBFBD><EFBFBD><EFBFBD>\<5C><>鱁j<E9B181><6A><EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/04/29 Version 2.05 <09>E<EFBFBD>L<EFBFBD><4C>6<EFBFBD><36>ނ<EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E<EFBFBD>Ђ炪<D082>Ȃ̎<C882><CC8E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/05/15 Version 2.051 <09>E1<45><31><EFBFBD>i❁j<E29D81><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/10/18 Version 2.052 <09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>Ƃւ<C682>A<EFBFBD><41><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƃ<EFBFBD><C682>܂܂<DC82><DC82>
|
|
||||||
<09>@<40><><EFBFBD><EFBFBD><EFBFBD>𒆐S<F0928690>Ɏ<EFBFBD><C98E>`<60><><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/12/23 Version 2.053 <09>E3<45><33><EFBFBD>i/f<><66><EFBFBD>j<EFBFBD><6A><EFBFBD>C<EFBFBD><43>
|
|
||||||
2014/12/25 Version 2.06 <09>E<EFBFBD>L<EFBFBD><4C>9<EFBFBD><39>ނ<EFBFBD>lj<EFBFBD>
|
|
||||||
2015/12/27 Version 2.061 <09>E<EFBFBD>c<EFBFBD><63><EFBFBD><EFBFBD><EFBFBD>p<EFBFBD>̎̂ĉ<CC82><C489><EFBFBD><EFBFBD>̃o<CC83><6F><EFBFBD><EFBFBD><EFBFBD>X<EFBFBD><EFBFBD>
|
|
||||||
2016/04/23 Version 2.07 <09>E<EFBFBD><45><EFBFBD><EFBFBD>2<EFBFBD><32><EFBFBD>A<EFBFBD>L<EFBFBD><4C>4<EFBFBD><34>ނ<EFBFBD>lj<EFBFBD>
|
|
||||||
<09>E<EFBFBD><45><EFBFBD><EFBFBD><EFBFBD>̎<EFBFBD><CC8E>`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ꕔ<EFBFBD>C<EFBFBD><43>
|
|
||||||
2018/04/01 Version 2.073 <09>E2<45><32><EFBFBD>i<EFBFBD>t<EFBFBD><74><EFBFBD>Q<EFBFBD><51><EFBFBD>A<EFBFBD>t<EFBFBD>^<5E>╄<EFBFBD>j<EFBFBD><6A><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
2019/01/19 Version 2.074 <09>E<EFBFBD>L<EFBFBD><4C>2<EFBFBD><32>ނ<EFBFBD>lj<EFBFBD>
|
|
||||||
2019/09/12 Version 2.075 <09>E<EFBFBD>u<EFBFBD>ߘa<DF98>v<EFBFBD>̍<EFBFBD><CC8D><EFBFBD><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
2019/11/16 Version 2.076 <09>E<EFBFBD><45><EFBFBD><EFBFBD>4<EFBFBD><34><EFBFBD><EFBFBD>lj<EFBFBD>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
2010-2019<31>@<40>}<7D><><EFBFBD>Z / <20>v<EFBFBD><76><EFBFBD>W<EFBFBD>F<EFBFBD>N<EFBFBD>gU-Fo
|
|
||||||
<EFBFBD>悭<EFBFBD><EFBFBD>Ȃ<EFBFBD><EFBFBD><EFBFBD> http://marusexijaxs.web.fc2.com/
|
|
||||||
<EFBFBD><EFBFBD><EFBFBD>Twitter: https://twitter.com/maruse_xijaxs
|
|
|
@ -1,113 +0,0 @@
|
||||||
|
|
||||||
【フォントのライセンスについて】
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
「よく飛ばない鳥」(以下「弊サイト」)の制作物を使用される場合の
|
|
||||||
ライセンスは本ドキュメントによります。
|
|
||||||
ご不明な点がございましたらお問い合わせください。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
弊サイトで公開している「すべてのフォント」
|
|
||||||
(以下「フォント」または「オリジナル」)は、
|
|
||||||
営利目的、非営利目的ともに
|
|
||||||
「字形あるいは著作権表示、もしくはその両方を改変しての二次配布」
|
|
||||||
以外は用途に制限を設けておりません。
|
|
||||||
|
|
||||||
クリエイティブ・コモンズ・ライセンスではCC BY-NDに相当します。
|
|
||||||
http://creativecommons.org/licenses/by-nd/4.0/deed.ja
|
|
||||||
|
|
||||||
webフォントとしてのご利用には制約を設けておりませんので、
|
|
||||||
サブセット化やWOFFコンバートは自由となります。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ここで述べる「字形の改変」「著作権表示の改変」についてですが、
|
|
||||||
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
1) 字形の改変
|
|
||||||
|
|
||||||
OK:
|
|
||||||
|
|
||||||
・太字、斜体、長体、平体、字間の調整、輪郭線のぼかしなど、
|
|
||||||
ペイントソフトやDTPソフトで一般的に想定しうる範囲内で
|
|
||||||
デザイン上の効果を施す行為。
|
|
||||||
|
|
||||||
・フォントファイル内の字形の「アウトライン」を
|
|
||||||
著しく変形させない程度にデザイン上の効果を施す行為。
|
|
||||||
|
|
||||||
例)・文字/記号の上にイラストやシルエットを重ねる
|
|
||||||
・横画の一部を長く引き延ばす など
|
|
||||||
|
|
||||||
|
|
||||||
NG:
|
|
||||||
|
|
||||||
・フォントファイル内の字形の「アウトライン」を
|
|
||||||
作者(=マルセ。以下「作者」)に無断で著しく変形させる行為。
|
|
||||||
|
|
||||||
フォントファイル内の字形をフォント制作ソフトなどで解析のうえ
|
|
||||||
アウトラインを著しく変形させ、
|
|
||||||
ウェブサイトや印刷物、動画などで使用する行為、
|
|
||||||
またはオリジナルとは別のフォントファイルを作成して配布する行為は
|
|
||||||
禁止とさせていただきます。
|
|
||||||
|
|
||||||
|
|
||||||
どうしても「字形を改変してのご利用」を希望される場合は、事前に作者まで
|
|
||||||
web拍手かTwitterなどでお問い合わせくださいますようお願い申し上げます。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2) 著作権表示の改変
|
|
||||||
|
|
||||||
フォントファイル内のフォント名表記、著作権表記、バージョン表記を改変し、
|
|
||||||
オリジナルとは別のフォントファイルを作成して配布する行為は
|
|
||||||
禁止とさせていただきます。
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
となります。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
また、フォントをご利用の際、作者への使用許可連絡は特に必要ございませんが、
|
|
||||||
可能な場合にはフォントを利用された制作物(ソフトウェアや印刷物、動画など)や、
|
|
||||||
その制作物に付随する媒体
|
|
||||||
(ウェブサイト、動画の説明文、ソフトウェアのreadmeテキストなど)に、
|
|
||||||
フォントの作品情報(クレジット)として以下の4項目
|
|
||||||
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
・フォント名
|
|
||||||
・作者名(=マルセ)
|
|
||||||
・弊サイト名(=よく飛ばない鳥)
|
|
||||||
・弊サイトURL(=http://marusexijaxs.web.fc2.com/)
|
|
||||||
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
を明記していただけると、たいへんありがたいです。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
以上
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
改訂履歴
|
|
||||||
|
|
||||||
2015/09/12 明文化
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
2010-2015 マルセ / プロジェクトU-Fo
|
|
||||||
よく飛ばない鳥 http://marusexijaxs.web.fc2.com/
|
|
||||||
作者Twitter: https://twitter.com/maruse_xijaxs
|
|
|
@ -1,114 +0,0 @@
|
||||||
|
|
||||||
拡張チェックポイントフォント
|
|
||||||
|
|
||||||
Version 1.052
|
|
||||||
|
|
||||||
OpenType Edition
|
|
||||||
|
|
||||||
|
|
||||||
readme
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
この度は、拡張チェックポイントフォントをダウンロードしていただき、
|
|
||||||
ありがとうございます。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
1. 拡張チェックポイントフォントとは
|
|
||||||
拡張チェックポイントフォントは、
|
|
||||||
チェックポイントフォント(圧縮ファイル同封)の字形を
|
|
||||||
文字通り「拡張」するフォントです。
|
|
||||||
|
|
||||||
フォント制作における文字データの収録には一部制約があり、
|
|
||||||
通常の方法では入れられない漢字・記号があります。
|
|
||||||
|
|
||||||
それらを救済すべく、拡張フォントは「メイン」に対する
|
|
||||||
「サブ」のフォントファイルという位置づけで、
|
|
||||||
約300字(ひらがな・カタカナ・漢字)を別データとして収録。
|
|
||||||
いわゆる「つちよし」の文字データを
|
|
||||||
「さむらいよし」の割り当て領域に入れ込むなど、
|
|
||||||
よく似た文字の領域に異体字などを組み入れています。
|
|
||||||
|
|
||||||
人名用漢字の異体字や、各種印刷標準字体にも対応しておりますので、
|
|
||||||
メインフォントと併せてインストールしていただき、
|
|
||||||
必要に応じて使い分けていただければと思います。
|
|
||||||
|
|
||||||
※変換割り当てについては、
|
|
||||||
添付ファイル「収録文字一覧.txt」をご覧ください。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2. 収録文字
|
|
||||||
・ひらがな…ガ行鼻濁音を示す5字
|
|
||||||
・カタカナ…ガ行鼻濁音を示す5字
|
|
||||||
・アルファベット…なし
|
|
||||||
・数字…なし
|
|
||||||
・記号…59種類
|
|
||||||
・漢字…人名用漢字の異体字や、印刷標準字体など、262字
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3. 動作環境
|
|
||||||
現在のところ、
|
|
||||||
Microsoft Windows XP、Vista、7、8、10、
|
|
||||||
およびMac OS Xで動作を確認しております。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
4. ご使用・配布について
|
|
||||||
ウェブサイトや印刷物、動画などでの使用のほか、
|
|
||||||
商用利用(同人誌などでの使用)についても、
|
|
||||||
特に制限は設けておりません。
|
|
||||||
ご自由にお使いいただければ制作者冥利に尽きます。
|
|
||||||
|
|
||||||
使用許可連絡も必要ございませんが、
|
|
||||||
よろしければ事後でも結構ですので、
|
|
||||||
web拍手やTwitterなどでご一報いただければありがたいです。
|
|
||||||
|
|
||||||
なお、字形あるいは著作権表示、
|
|
||||||
もしくはその両方を改変しての二次配布はご容赦くださいませ。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5. 免責
|
|
||||||
当フォントの使用により万が一生じた損害に対し、
|
|
||||||
当方は一切の責任を負いかねます。あらかじめご了承くださいませ。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
6. その他
|
|
||||||
収録されている文字・記号の種類および字形、文字幅、変換割り当て、
|
|
||||||
またダウンロード用のリンクなどは予告なく変更する場合があります。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
7. 更新履歴
|
|
||||||
2013/09/12 Version 1.00 ・公開
|
|
||||||
2014/02/08 Version 1.01 ・文字・記号52種類を追加
|
|
||||||
2014/03/03 Version 1.02 ・文字・記号6種類を追加
|
|
||||||
・1字(!!)を修正
|
|
||||||
2014/04/29 Version 1.03 ・記号7種類を追加
|
|
||||||
2014/10/18 Version 1.031 ・こざとへん、おおざとが含まれる
|
|
||||||
漢字の字形を修正
|
|
||||||
2014/12/23 Version 1.032 ・1字(バックスラッシュ)を修正
|
|
||||||
・隆の旧字の字体違いを「隆」にも変換割り当て
|
|
||||||
・リターン記号を「↑」にも変換割り当て
|
|
||||||
・枡記号を「枡」にも変換割り当て
|
|
||||||
・ウェーヴィーダッシュの変換割り当てを
|
|
||||||
縦書きの「ー」「~」のみに
|
|
||||||
(横書きの割り当ては削除しました)
|
|
||||||
・リターン記号、左向黒三角、右向黒三角、
|
|
||||||
左向白三角、右向白三角、左半円、右半円を
|
|
||||||
それぞれの縦書き用記号にも変換割り当て
|
|
||||||
2016/04/23 Version 1.05 ・記号9種類を追加
|
|
||||||
・1字(張の簡体字)を訂正
|
|
||||||
2019/11/16 Version 1.052 ・漢字1字を追加
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------------
|
|
||||||
|
|
||||||
2010-2019 マルセ / プロジェクトU-Fo
|
|
||||||
よく飛ばない鳥 http://marusexijaxs.web.fc2.com/
|
|
||||||
作者Twitter: https://twitter.com/maruse_xijaxs
|
|
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><path fill="#f35325" d="M0 0h10v10H0z"/><path fill="#81bc06" d="M11 0h10v10H11z"/><path fill="#05a6f0" d="M0 11h10v10H0z"/><path fill="#ffba08" d="M11 11h10v10H11z"/></svg>
|
|
Before Width: | Height: | Size: 232 B |
Before Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 3.5 MiB |
|
@ -1,4 +1,4 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
|
@ -1,39 +0,0 @@
|
||||||
// @refresh reload
|
|
||||||
import "@fortawesome/fontawesome-svg-core/styles.css"
|
|
||||||
import { Link, Meta, MetaProvider, Title } from "@solidjs/meta"
|
|
||||||
import { Router } from "@solidjs/router"
|
|
||||||
import { FileRoutes } from "@solidjs/start"
|
|
||||||
import { Suspense } from "solid-js"
|
|
||||||
import "./styles/App.scss"
|
|
||||||
import "./styles/globals.scss"
|
|
||||||
import "./styles/grid.scss"
|
|
||||||
import "./styles/grid2.scss"
|
|
||||||
import "./styles/root.css"
|
|
||||||
|
|
||||||
export default function App() {
|
|
||||||
return (
|
|
||||||
<Router
|
|
||||||
root={(props) => (
|
|
||||||
<MetaProvider>
|
|
||||||
<Title>Leaky Ships</Title>
|
|
||||||
<Meta charset="utf-8" />
|
|
||||||
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<Link rel="manifest" href="/manifest.json" />
|
|
||||||
<Link rel="icon" href="/favicon.ico" />
|
|
||||||
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<Meta name="theme-color" content="#000000" />
|
|
||||||
<Meta
|
|
||||||
name="description"
|
|
||||||
content="Battleship web app with react frontend and ASP .NET backend"
|
|
||||||
/>
|
|
||||||
<Link rel="apple-touch-icon" href="/logo192.png" />
|
|
||||||
|
|
||||||
<Suspense>{props.children}</Suspense>
|
|
||||||
</MetaProvider>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<FileRoutes />
|
|
||||||
</Router>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
import { createSignal } from "solid-js"
|
|
||||||
|
|
||||||
function Bluetooth() {
|
|
||||||
const [startDisabled, setStartDisabled] = createSignal(true)
|
|
||||||
const [stopDisabled, setStopDisabled] = createSignal(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 (
|
|
||||||
<div>
|
|
||||||
<button id="read" class="bluetooth" onClick={read}>
|
|
||||||
Connect with BLE device
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
id="start"
|
|
||||||
class="bluetooth"
|
|
||||||
disabled={startDisabled()}
|
|
||||||
onClick={start}
|
|
||||||
>
|
|
||||||
Start
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
id="stop"
|
|
||||||
class="bluetooth"
|
|
||||||
disabled={stopDisabled()}
|
|
||||||
onClick={stop}
|
|
||||||
>
|
|
||||||
Stop
|
|
||||||
</button>
|
|
||||||
<p>
|
|
||||||
<span
|
|
||||||
class="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
|
|
||||||
class="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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Bluetooth
|
|
|
@ -1,22 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
|
|
||||||
function BurgerMenu(props: { onClick?: () => void; blur?: boolean }) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
id="menu"
|
|
||||||
class={classNames(
|
|
||||||
"absolute left-4 top-4 flex h-16 w-16 items-center justify-center rounded-lg border-b-2 border-shield-gray bg-grayish shadow-lg duration-100 active:border-b-0 active:border-t-2 md:left-6 md:top-6 md:h-20 md:w-20 md:rounded-xl md:border-b-4 md:active:border-t-4 lg:left-8 lg:top-8 xl:left-12 xl:top-12 xl:h-24 xl:w-24",
|
|
||||||
{ "blur-sm": props.blur },
|
|
||||||
)}
|
|
||||||
onClick={() => props.onClick && setTimeout(props.onClick, 200)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="pixelart h-12 w-12 md:h-16 md:w-16 xl:h-20 xl:w-20"
|
|
||||||
src="/assets/burger-menu.png"
|
|
||||||
alt="Burger Menu"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BurgerMenu
|
|
|
@ -1,151 +0,0 @@
|
||||||
import {
|
|
||||||
FaSymbol,
|
|
||||||
FlipProp,
|
|
||||||
IconDefinition,
|
|
||||||
IconProp,
|
|
||||||
PullProp,
|
|
||||||
RotateProp,
|
|
||||||
SizeProp,
|
|
||||||
Transform,
|
|
||||||
} from "@fortawesome/fontawesome-svg-core"
|
|
||||||
import { Show, type JSX } from "solid-js"
|
|
||||||
|
|
||||||
export interface FontAwesomeIconProps
|
|
||||||
extends Omit<
|
|
||||||
JSX.SvgSVGAttributes<SVGSVGElement>,
|
|
||||||
"children" | "mask" | "transform"
|
|
||||||
> {
|
|
||||||
icon: IconDefinition
|
|
||||||
mask?: IconProp
|
|
||||||
maskId?: string
|
|
||||||
color?: string
|
|
||||||
spin?: boolean
|
|
||||||
spinPulse?: boolean
|
|
||||||
spinReverse?: boolean
|
|
||||||
pulse?: boolean
|
|
||||||
beat?: boolean
|
|
||||||
fade?: boolean
|
|
||||||
beatFade?: boolean
|
|
||||||
bounce?: boolean
|
|
||||||
shake?: boolean
|
|
||||||
flash?: boolean
|
|
||||||
border?: boolean
|
|
||||||
fixedWidth?: boolean
|
|
||||||
inverse?: boolean
|
|
||||||
listItem?: boolean
|
|
||||||
flip?: FlipProp
|
|
||||||
size?: SizeProp
|
|
||||||
pull?: PullProp
|
|
||||||
rotation?: RotateProp
|
|
||||||
transform?: string | Transform
|
|
||||||
symbol?: FaSymbol
|
|
||||||
style?: JSX.CSSProperties
|
|
||||||
tabIndex?: number
|
|
||||||
title?: string
|
|
||||||
titleId?: string
|
|
||||||
swapOpacity?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const idPool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
function nextUniqueId() {
|
|
||||||
let size = 12
|
|
||||||
let id = ""
|
|
||||||
|
|
||||||
while (size-- > 0) {
|
|
||||||
id += idPool[(Math.random() * 62) | 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
function Path(props: { d: string | string[] }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{typeof props.d === "string" ? (
|
|
||||||
<path fill="currentColor" d={props.d} />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<path class="fa-secondary" fill="currentColor" d={props.d[0]} />
|
|
||||||
<path class="fa-primary" fill="currentColor" d={props.d[1]} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FontAwesomeIcon(props: FontAwesomeIconProps) {
|
|
||||||
const titleId = () =>
|
|
||||||
props.title
|
|
||||||
? "svg-inline--fa-title-".concat(props.titleId || nextUniqueId())
|
|
||||||
: undefined
|
|
||||||
// Get CSS class list from the props object
|
|
||||||
function attributes() {
|
|
||||||
const defaultClasses = {
|
|
||||||
"svg-inline--fa": true,
|
|
||||||
[`fa-${props.icon.iconName}`]: true,
|
|
||||||
[props.class ?? ""]:
|
|
||||||
typeof props.class !== "undefined" && props.class !== null,
|
|
||||||
...props.classList,
|
|
||||||
}
|
|
||||||
|
|
||||||
// map of CSS class names to properties
|
|
||||||
const faClasses = {
|
|
||||||
"fa-beat": props.beat,
|
|
||||||
"fa-fade": props.fade,
|
|
||||||
"fa-beat-fade": props.beatFade,
|
|
||||||
"fa-bounce": props.bounce,
|
|
||||||
"fa-shake": props.shake,
|
|
||||||
"fa-flash": props.flash,
|
|
||||||
"fa-spin": props.spin,
|
|
||||||
"fa-spin-reverse": props.spinReverse,
|
|
||||||
"fa-spin-pulse": props.spinPulse,
|
|
||||||
"fa-pulse": props.pulse,
|
|
||||||
"fa-fw": props.fixedWidth,
|
|
||||||
"fa-inverse": props.inverse,
|
|
||||||
"fa-border": props.border,
|
|
||||||
"fa-li": props.listItem,
|
|
||||||
"fa-flip": typeof props.flip !== "undefined" && props.flip !== null,
|
|
||||||
"fa-flip-horizontal":
|
|
||||||
props.flip === "horizontal" || props.flip === "both",
|
|
||||||
"fa-flip-vertical": props.flip === "vertical" || props.flip === "both",
|
|
||||||
[`fa-${props.size}`]:
|
|
||||||
typeof props.size !== "undefined" && props.size !== null,
|
|
||||||
[`fa-rotate-${props.rotation}`]:
|
|
||||||
typeof props.rotation !== "undefined" && props.size !== null,
|
|
||||||
[`fa-pull-${props.pull}`]:
|
|
||||||
typeof props.pull !== "undefined" && props.pull !== null,
|
|
||||||
"fa-swap-opacity": props.swapOpacity,
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributes = {
|
|
||||||
focusable: !!props.title,
|
|
||||||
"aria-hidden": !props.title,
|
|
||||||
role: "img",
|
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
|
||||||
"aria-labelledby": titleId(),
|
|
||||||
"data-prefix": props.icon.prefix,
|
|
||||||
"data-icon": props.icon.iconName,
|
|
||||||
"data-fa-transform": props.transform,
|
|
||||||
"data-fa-mask": props.mask,
|
|
||||||
"data-fa-mask-id": props.maskId,
|
|
||||||
"data-fa-symbol": props.symbol,
|
|
||||||
tabIndex: props.tabIndex,
|
|
||||||
classList: { ...defaultClasses, ...faClasses },
|
|
||||||
color: props.color,
|
|
||||||
style: props.style,
|
|
||||||
viewBox: `0 0 ${props.icon.icon[0]} ${props.icon.icon[1]}`,
|
|
||||||
} as const
|
|
||||||
|
|
||||||
// return the complete class list
|
|
||||||
return attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg {...attributes()}>
|
|
||||||
<Show when={props.title}>
|
|
||||||
<title id={titleId()}>{props.title}</title>
|
|
||||||
</Show>
|
|
||||||
<Path d={props.icon.icon[4]} />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
import { For } from "solid-js"
|
|
||||||
import {
|
|
||||||
gameProps,
|
|
||||||
mouseCursor,
|
|
||||||
removeShip,
|
|
||||||
setGameProps,
|
|
||||||
setMouseCursor,
|
|
||||||
setShips,
|
|
||||||
setTarget,
|
|
||||||
targetPreview,
|
|
||||||
} from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import {
|
|
||||||
borderCN,
|
|
||||||
compiledHits,
|
|
||||||
cornerCN,
|
|
||||||
fieldIndex,
|
|
||||||
intersectingShip,
|
|
||||||
isAlreadyHit,
|
|
||||||
overlapsWithAnyBorder,
|
|
||||||
shipProps,
|
|
||||||
targetList,
|
|
||||||
} from "~/lib/utils/helpers"
|
|
||||||
import { count } from "./Gamefield"
|
|
||||||
|
|
||||||
type TilesType = {
|
|
||||||
key: number
|
|
||||||
isGameTile: boolean
|
|
||||||
className: string
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
function settingTarget(
|
|
||||||
isGameTile: boolean,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
index: 0 | 1,
|
|
||||||
{
|
|
||||||
activeIndex,
|
|
||||||
ships,
|
|
||||||
}: Pick<ReturnType<typeof useSession>, "activeIndex" | "ships">,
|
|
||||||
) {
|
|
||||||
if (gameProps.gameState === "running") {
|
|
||||||
const list = targetList(targetPreview(), gameProps.mode)
|
|
||||||
if (
|
|
||||||
!isGameTile ||
|
|
||||||
!list.filter(
|
|
||||||
({ x, y }) => !isAlreadyHit(x, y, compiledHits(activeIndex())),
|
|
||||||
).length
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if (!overlapsWithAnyBorder(targetPreview(), gameProps.mode))
|
|
||||||
setTarget({
|
|
||||||
show: true,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
orientation: targetPreview().orientation,
|
|
||||||
})
|
|
||||||
} else if (
|
|
||||||
gameProps.gameState === "starting" &&
|
|
||||||
targetPreview().show &&
|
|
||||||
!intersectingShip(
|
|
||||||
ships(),
|
|
||||||
shipProps(ships(), gameProps.mode, targetPreview()),
|
|
||||||
).score
|
|
||||||
) {
|
|
||||||
setMouseCursor((e) => ({ ...e, shouldShow: false }))
|
|
||||||
setShips(
|
|
||||||
[...ships(), shipProps(ships(), gameProps.mode, targetPreview())],
|
|
||||||
index,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onClick(
|
|
||||||
props: TilesType,
|
|
||||||
{ selfIndex, activeIndex, ships }: ReturnType<typeof useSession>,
|
|
||||||
) {
|
|
||||||
const sIndex = selfIndex()
|
|
||||||
if (!sIndex) return
|
|
||||||
if (gameProps.gameState === "running") {
|
|
||||||
settingTarget(props.isGameTile, props.x, props.y, sIndex.i, {
|
|
||||||
activeIndex,
|
|
||||||
ships,
|
|
||||||
})
|
|
||||||
} else if (gameProps.gameState === "starting") {
|
|
||||||
const { index } = intersectingShip(ships(), {
|
|
||||||
...mouseCursor(),
|
|
||||||
size: 1,
|
|
||||||
variant: 0,
|
|
||||||
orientation: "h",
|
|
||||||
})
|
|
||||||
if (typeof index === "undefined")
|
|
||||||
settingTarget(props.isGameTile, props.x, props.y, sIndex.i, {
|
|
||||||
activeIndex,
|
|
||||||
ships,
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
const ship = ships()[index]
|
|
||||||
setGameProps("mode", ship.size - 2)
|
|
||||||
removeShip(ship, sIndex.i)
|
|
||||||
setMouseCursor((e) => ({ ...e, shouldShow: true }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseEnter(
|
|
||||||
props: TilesType,
|
|
||||||
{ ships }: ReturnType<typeof useSession>,
|
|
||||||
) {
|
|
||||||
setMouseCursor({
|
|
||||||
x: props.x,
|
|
||||||
y: props.y,
|
|
||||||
shouldShow:
|
|
||||||
props.isGameTile &&
|
|
||||||
(gameProps.gameState === "starting"
|
|
||||||
? intersectingShip(
|
|
||||||
ships(),
|
|
||||||
shipProps(ships(), gameProps.mode, {
|
|
||||||
x: props.x,
|
|
||||||
y: props.y,
|
|
||||||
orientation: targetPreview().orientation,
|
|
||||||
}),
|
|
||||||
true,
|
|
||||||
).score < 2
|
|
||||||
: true),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function BorderTiles() {
|
|
||||||
const sessionProps = useSession()
|
|
||||||
|
|
||||||
const tilesProperties: TilesType[] = []
|
|
||||||
|
|
||||||
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 className = classNames.join(" ")
|
|
||||||
tilesProperties.push({
|
|
||||||
key,
|
|
||||||
className,
|
|
||||||
isGameTile,
|
|
||||||
x: x + 1,
|
|
||||||
y: y + 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<For each={tilesProperties}>
|
|
||||||
{(props) => (
|
|
||||||
<div
|
|
||||||
class={props.className}
|
|
||||||
style={{ "--x": props.x, "--y": props.y }}
|
|
||||||
onClick={() => onClick(props, sessionProps)}
|
|
||||||
onMouseEnter={() => onMouseEnter(props, sessionProps)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BorderTiles
|
|
|
@ -1,369 +0,0 @@
|
||||||
import {
|
|
||||||
faSquare2,
|
|
||||||
faSquare3,
|
|
||||||
faSquare4,
|
|
||||||
} from "@fortawesome/pro-regular-svg-icons"
|
|
||||||
import {
|
|
||||||
faBroomWide,
|
|
||||||
faCheck,
|
|
||||||
faComments,
|
|
||||||
faEye,
|
|
||||||
faEyeSlash,
|
|
||||||
faFlag,
|
|
||||||
faGlasses,
|
|
||||||
faLock,
|
|
||||||
faPalette,
|
|
||||||
faReply,
|
|
||||||
faRotate,
|
|
||||||
faScribble,
|
|
||||||
faShip,
|
|
||||||
faSparkles,
|
|
||||||
faSwords,
|
|
||||||
faXmark,
|
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { socket } from "~/lib/socket"
|
|
||||||
import { modes } from "~/lib/utils/helpers"
|
|
||||||
// import { Icons, toast } from "react-toastify"
|
|
||||||
import { useNavigate } from "@solidjs/router"
|
|
||||||
import { For, Show, createEffect } from "solid-js"
|
|
||||||
import { clearDrawing } from "~/hooks/useDraw"
|
|
||||||
import {
|
|
||||||
color,
|
|
||||||
setEnable,
|
|
||||||
setShouldHide,
|
|
||||||
shouldHide,
|
|
||||||
} from "~/hooks/useDrawProps"
|
|
||||||
import {
|
|
||||||
gameProps,
|
|
||||||
reset,
|
|
||||||
setGameProps,
|
|
||||||
setGameSetting,
|
|
||||||
setIsReadyFor,
|
|
||||||
setTarget,
|
|
||||||
setTargetPreview,
|
|
||||||
target,
|
|
||||||
users,
|
|
||||||
} from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import { EventBarModes } from "../../interfaces/frontend"
|
|
||||||
import Item from "./Item"
|
|
||||||
|
|
||||||
function EventBar() {
|
|
||||||
const { selfIndex, selfIsActiveIndex, selfUser, ships } = useSession()
|
|
||||||
const navigator = useNavigate()
|
|
||||||
|
|
||||||
const items = (): EventBarModes => ({
|
|
||||||
main: [
|
|
||||||
{
|
|
||||||
icon: "burger-menu",
|
|
||||||
text: "Menu",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "menu")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faSwords,
|
|
||||||
text: "Attack",
|
|
||||||
showWhen: () =>
|
|
||||||
gameProps.gameState === "running" &&
|
|
||||||
(selfIsActiveIndex() || gameProps.menu !== "main"),
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "moves")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faShip,
|
|
||||||
text: "Ships",
|
|
||||||
showWhen: () => gameProps.gameState !== "running",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "moves")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "pen",
|
|
||||||
text: "Draw",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "draw")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "gear",
|
|
||||||
text: "Settings",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "settings")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
menu: [
|
|
||||||
{
|
|
||||||
icon: faFlag,
|
|
||||||
text: "Surrender",
|
|
||||||
iconColor: "darkred",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "surrender")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
moves:
|
|
||||||
gameProps.gameState === "running"
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
icon: "scope",
|
|
||||||
text: "Fire missile",
|
|
||||||
enabled: gameProps.mode === 0,
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("mode", 0)
|
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "torpedo",
|
|
||||||
text: "Fire torpedo",
|
|
||||||
enabled: gameProps.mode === 1 || gameProps.mode === 2,
|
|
||||||
amount:
|
|
||||||
2 -
|
|
||||||
(selfUser()?.moves.filter(
|
|
||||||
(e) => e.type === "htorpedo" || e.type === "vtorpedo",
|
|
||||||
).length ?? 0),
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("mode", 1)
|
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: "radar",
|
|
||||||
text: "Radar scan",
|
|
||||||
enabled: gameProps.mode === 3,
|
|
||||||
amount:
|
|
||||||
1 -
|
|
||||||
(selfUser()?.moves.filter((e) => e.type === "radar").length ??
|
|
||||||
0),
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("mode", 3)
|
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
icon: faSquare2,
|
|
||||||
text: "Minensucher",
|
|
||||||
amount: 1 - ships().filter((e) => e.size === 2).length,
|
|
||||||
callback: () => {
|
|
||||||
if (1 - ships().filter((e) => e.size === 2).length === 0) return
|
|
||||||
setGameProps("mode", 0)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faSquare3,
|
|
||||||
text: "Kreuzer",
|
|
||||||
amount: 3 - ships().filter((e) => e.size === 3).length,
|
|
||||||
callback: () => {
|
|
||||||
if (3 - ships().filter((e) => e.size === 3).length === 0) return
|
|
||||||
setGameProps("mode", 1)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faSquare4,
|
|
||||||
text: "Schlachtschiff",
|
|
||||||
amount: 2 - ships().filter((e) => e.size === 4).length,
|
|
||||||
callback: () => {
|
|
||||||
if (2 - ships().filter((e) => e.size === 4).length === 0) return
|
|
||||||
setGameProps("mode", 2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faRotate,
|
|
||||||
text: "Rotate",
|
|
||||||
callback: () => {
|
|
||||||
setTargetPreview((t) => ({
|
|
||||||
...t,
|
|
||||||
orientation: t.orientation === "h" ? "v" : "h",
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
draw: [
|
|
||||||
{
|
|
||||||
icon: faBroomWide,
|
|
||||||
text: "Clear",
|
|
||||||
showWhen: selfIsActiveIndex,
|
|
||||||
callback: () => clearDrawing(selfIndex()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faPalette,
|
|
||||||
text: "Color",
|
|
||||||
showWhen: selfIsActiveIndex,
|
|
||||||
iconColor: color(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: shouldHide() ? faEye : faEyeSlash,
|
|
||||||
text: shouldHide() ? "Show" : "Hide",
|
|
||||||
callback: () => {
|
|
||||||
setShouldHide((e) => !e)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
settings: [
|
|
||||||
{
|
|
||||||
icon: faGlasses,
|
|
||||||
text: "Spectators",
|
|
||||||
disabled: !gameProps.allowSpectators,
|
|
||||||
callback: setGameSetting({
|
|
||||||
allowSpectators: !gameProps.allowSpectators,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faSparkles,
|
|
||||||
text: "Specials",
|
|
||||||
disabled: !gameProps.allowSpecials,
|
|
||||||
callback: setGameSetting({
|
|
||||||
allowSpecials: !gameProps.allowSpecials,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faComments,
|
|
||||||
text: "Chat",
|
|
||||||
disabled: !gameProps.allowChat,
|
|
||||||
callback: setGameSetting({ allowChat: !gameProps.allowChat }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faScribble,
|
|
||||||
text: "Mark/Draw",
|
|
||||||
disabled: !gameProps.allowMarkDraw,
|
|
||||||
callback: setGameSetting({
|
|
||||||
allowMarkDraw: !gameProps.allowMarkDraw,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
surrender: [
|
|
||||||
{
|
|
||||||
icon: faCheck,
|
|
||||||
text: "Yes",
|
|
||||||
iconColor: "green",
|
|
||||||
callback: async () => {
|
|
||||||
socket.emit("gameState", "aborted")
|
|
||||||
navigator("/")
|
|
||||||
reset()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: faXmark,
|
|
||||||
text: "No",
|
|
||||||
iconColor: "red",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "main")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (
|
|
||||||
gameProps.menu !== "moves" ||
|
|
||||||
gameProps.gameState !== "starting" ||
|
|
||||||
gameProps.mode < 0 ||
|
|
||||||
items().moves[gameProps.mode].amount
|
|
||||||
)
|
|
||||||
return
|
|
||||||
const index = items().moves.findIndex((e) => e.amount)
|
|
||||||
setGameProps("mode", index)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
setEnable(gameProps.menu === "draw")
|
|
||||||
})
|
|
||||||
|
|
||||||
// createEffect(() => {
|
|
||||||
// if (gameProps.gameState !== "running") return
|
|
||||||
|
|
||||||
// const toastId = "otherPlayer"
|
|
||||||
// if (selfIsActiveIndex) toast.dismiss(toastId)
|
|
||||||
// else
|
|
||||||
// toast.info("Waiting for other player...", {
|
|
||||||
// toastId,
|
|
||||||
// position: "top-right",
|
|
||||||
// icon: Icons.spinner(),
|
|
||||||
// autoClose: false,
|
|
||||||
// hideProgressBar: true,
|
|
||||||
// closeButton: false,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// toastId = "connect_error"
|
|
||||||
// const isActive = toast.isActive(toastId)
|
|
||||||
// console.log(toastId, isActive)
|
|
||||||
// if (isActive)
|
|
||||||
// toast.update(toastId, {
|
|
||||||
// autoClose: 5000,
|
|
||||||
// })
|
|
||||||
// else
|
|
||||||
// toast.warn("Spie", { toastId })
|
|
||||||
// })
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="event-bar">
|
|
||||||
<Show when={gameProps.menu !== "main"}>
|
|
||||||
<Item
|
|
||||||
{...{
|
|
||||||
icon: faReply,
|
|
||||||
text: "Return",
|
|
||||||
iconColor: "#555",
|
|
||||||
callback: () => {
|
|
||||||
setGameProps("menu", "main")
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
<For each={items()[gameProps.menu]}>
|
|
||||||
{(e) => (
|
|
||||||
<Show when={!e?.showWhen || e?.showWhen()}>
|
|
||||||
<Item {...e} />
|
|
||||||
</Show>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
<Show when={gameProps.menu === "moves"}>
|
|
||||||
<Item
|
|
||||||
{...{
|
|
||||||
icon: selfUser()?.isReady ? faLock : faCheck,
|
|
||||||
text: selfUser()?.isReady ? "unready" : "Done",
|
|
||||||
disabled:
|
|
||||||
gameProps.gameState === "starting"
|
|
||||||
? gameProps.mode >= 0
|
|
||||||
: undefined,
|
|
||||||
enabled:
|
|
||||||
gameProps.gameState === "running" &&
|
|
||||||
gameProps.mode >= 0 &&
|
|
||||||
target().show,
|
|
||||||
callback: () => {
|
|
||||||
const sIndex = selfIndex()
|
|
||||||
if (!sIndex) return
|
|
||||||
switch (gameProps.gameState) {
|
|
||||||
case "starting":
|
|
||||||
const isReady = !users[sIndex.i]?.isReady
|
|
||||||
setIsReadyFor({ isReady, i: sIndex.i })
|
|
||||||
socket.emit("isReady", isReady)
|
|
||||||
break
|
|
||||||
|
|
||||||
case "running":
|
|
||||||
const moves = selfUser()?.moves
|
|
||||||
const length = moves?.length
|
|
||||||
const props = {
|
|
||||||
type: modes[gameProps.mode].type,
|
|
||||||
x: target().x,
|
|
||||||
y: target().y,
|
|
||||||
orientation: target().orientation,
|
|
||||||
index: length ?? 0,
|
|
||||||
}
|
|
||||||
socket.emit("dispatchMove", props)
|
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EventBar
|
|
|
@ -1,13 +0,0 @@
|
||||||
function FogImages() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<img class="fog left" src={`/fog/fog2.png`} alt={`fog1.png`} />
|
|
||||||
<img class="fog right" src={`/fog/fog2.png`} alt={`fog1.png`} />
|
|
||||||
<img class="fog top" src={`/fog/fog2.png`} alt={`fog1.png`} />
|
|
||||||
<img class="fog bottom" src={`/fog/fog2.png`} alt={`fog1.png`} />
|
|
||||||
<img class="fog middle" src={`/fog/fog4.png`} alt={`fog4.png`} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FogImages
|
|
|
@ -1,139 +0,0 @@
|
||||||
// import Bluetooth from "./Bluetooth"
|
|
||||||
// import FogImages from "./FogImages"
|
|
||||||
// import { toast } from "react-toastify"
|
|
||||||
import { useNavigate } from "@solidjs/router"
|
|
||||||
import { createEffect, onCleanup } from "solid-js"
|
|
||||||
import BorderTiles from "~/components/Gamefield/BorderTiles"
|
|
||||||
import EventBar from "~/components/Gamefield/EventBar"
|
|
||||||
import HitElems from "~/components/Gamefield/HitElems"
|
|
||||||
import Targets from "~/components/Gamefield/Targets"
|
|
||||||
import { DrawingCanvas } from "~/hooks/useDraw"
|
|
||||||
import { setFrameSize } from "~/hooks/useDrawProps"
|
|
||||||
import {
|
|
||||||
full,
|
|
||||||
gameProps,
|
|
||||||
mouseCursor,
|
|
||||||
reset,
|
|
||||||
setGameProps,
|
|
||||||
setMouseCursor,
|
|
||||||
setTargetPreview,
|
|
||||||
target,
|
|
||||||
users,
|
|
||||||
} from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import useSocket from "~/hooks/useSocket"
|
|
||||||
import { socket } from "~/lib/socket"
|
|
||||||
import { overlapsWithAnyBorder } from "~/lib/utils/helpers"
|
|
||||||
import Labeling from "./Labeling"
|
|
||||||
import Ships from "./Ships"
|
|
||||||
|
|
||||||
export const count = 12
|
|
||||||
|
|
||||||
function Gamefield() {
|
|
||||||
let frameRef: HTMLDivElement
|
|
||||||
const { ships } = useSession()
|
|
||||||
const navigator = useNavigate()
|
|
||||||
const { isConnected } = useSocket()
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (
|
|
||||||
gameProps.gameState !== "starting" ||
|
|
||||||
!users[0]?.isReady ||
|
|
||||||
!users[1]?.isReady
|
|
||||||
)
|
|
||||||
return
|
|
||||||
socket.emit("ships", ships())
|
|
||||||
socket.emit("gameState", "running")
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (gameProps.gameId || !isConnected) return
|
|
||||||
socket.emit("update", full)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (gameProps.mode < 0) return
|
|
||||||
const { x, y, show } = target()
|
|
||||||
const { shouldShow, ...position } = mouseCursor()
|
|
||||||
if (
|
|
||||||
!shouldShow ||
|
|
||||||
(gameProps.gameState === "running" &&
|
|
||||||
overlapsWithAnyBorder(position, gameProps.mode))
|
|
||||||
)
|
|
||||||
setTargetPreview((t) => ({ ...t, show: false }))
|
|
||||||
else {
|
|
||||||
setTargetPreview((t) => ({
|
|
||||||
...t,
|
|
||||||
...position,
|
|
||||||
show: !show || x !== position.x || y !== position.y,
|
|
||||||
}))
|
|
||||||
const handleKeyPress = (event: KeyboardEvent) => {
|
|
||||||
if (event.key !== "r") return
|
|
||||||
if (gameProps.gameState === "starting") {
|
|
||||||
setTargetPreview((t) => ({
|
|
||||||
...t,
|
|
||||||
orientation: t.orientation === "h" ? "v" : "h",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
gameProps.gameState === "running" &&
|
|
||||||
(gameProps.mode === 1 || gameProps.mode === 2)
|
|
||||||
)
|
|
||||||
setGameProps("mode", gameProps.mode === 1 ? 2 : 1)
|
|
||||||
}
|
|
||||||
document.addEventListener("keydown", handleKeyPress)
|
|
||||||
onCleanup(() => {
|
|
||||||
document.removeEventListener("keydown", handleKeyPress)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (gameProps.gameState !== "aborted") return
|
|
||||||
// toast.info("Enemy gave up!")
|
|
||||||
navigator("/")
|
|
||||||
reset()
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
function handleResize() {
|
|
||||||
const rect = frameRef.getBoundingClientRect()
|
|
||||||
setFrameSize({ x: rect.width, y: rect.height })
|
|
||||||
}
|
|
||||||
handleResize()
|
|
||||||
window.addEventListener("resize", handleResize)
|
|
||||||
onCleanup(() => removeEventListener("resize", handleResize))
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div id="gamefield">
|
|
||||||
{/* <Bluetooth /> */}
|
|
||||||
<div
|
|
||||||
id="game-frame"
|
|
||||||
style={{ "--i": count }}
|
|
||||||
onMouseLeave={() =>
|
|
||||||
setMouseCursor((e) => ({ ...e, shouldShow: false }))
|
|
||||||
}
|
|
||||||
ref={frameRef!}
|
|
||||||
>
|
|
||||||
<BorderTiles />
|
|
||||||
|
|
||||||
{/* Collumn lettes and row numbers */}
|
|
||||||
<Labeling />
|
|
||||||
|
|
||||||
<Ships />
|
|
||||||
|
|
||||||
<HitElems />
|
|
||||||
|
|
||||||
{/* <FogImages /> */}
|
|
||||||
|
|
||||||
<Targets />
|
|
||||||
|
|
||||||
<DrawingCanvas />
|
|
||||||
</div>
|
|
||||||
<EventBar />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Gamefield
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { faCrosshairs } from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { faRadar } from "@fortawesome/pro-thin-svg-icons"
|
|
||||||
import classNames from "classnames"
|
|
||||||
import {} from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { PointerProps } from "../../interfaces/frontend"
|
|
||||||
|
|
||||||
function GamefieldPointer(
|
|
||||||
props: PointerProps & {
|
|
||||||
preview?: boolean
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const isRadar = () => props.type === "radar"
|
|
||||||
const style = () =>
|
|
||||||
!(isRadar() && !props.edges.filter((s) => s).length)
|
|
||||||
? { "--x": props.x, "--y": props.y }
|
|
||||||
: {
|
|
||||||
"--x1": props.x - 1,
|
|
||||||
"--x2": props.x + 2,
|
|
||||||
"--y1": props.y - 1,
|
|
||||||
"--y2": props.y + 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classNames("hit-svg", "target", props.type, ...props.edges, {
|
|
||||||
preview: props.preview,
|
|
||||||
show: props.show,
|
|
||||||
imply: props.imply,
|
|
||||||
})}
|
|
||||||
style={style()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={!isRadar() ? faCrosshairs : faRadar} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GamefieldPointer
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { For } from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import { compiledHits } from "~/lib/utils/helpers"
|
|
||||||
import { Hit } from "../../interfaces/frontend"
|
|
||||||
|
|
||||||
function HitElems(props: { hits?: Hit[]; colorOverride?: string }) {
|
|
||||||
const { activeIndex } = useSession()
|
|
||||||
const hits = () => props?.hits
|
|
||||||
const colorOverride = () => props?.colorOverride
|
|
||||||
|
|
||||||
return (
|
|
||||||
<For each={hits() ?? compiledHits(activeIndex())}>
|
|
||||||
{(props) => (
|
|
||||||
<div class="hit-svg" style={{ "--x": props.x, "--y": props.y }}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={props.hit ? faBurst : faXmark}
|
|
||||||
style={{
|
|
||||||
color: colorOverride() || (props.hit ? "red" : undefined),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HitElems
|
|
|
@ -1,63 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { BlockPicker } from "solid-color"
|
|
||||||
import { Show, createSignal } from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { color, colors, setColor } from "~/hooks/useDrawProps"
|
|
||||||
import { ItemProps } from "../../interfaces/frontend"
|
|
||||||
|
|
||||||
function Item(props: ItemProps) {
|
|
||||||
const isColor = () => props.text === "Color"
|
|
||||||
const [active, setActive] = createSignal(false)
|
|
||||||
let cpRef: HTMLDivElement
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="item">
|
|
||||||
<Show when={isColor()}>
|
|
||||||
<div
|
|
||||||
ref={cpRef!}
|
|
||||||
class={classNames("color-picker-wrapper", { active: active() })}
|
|
||||||
>
|
|
||||||
<BlockPicker
|
|
||||||
color={color()}
|
|
||||||
onChange={(e) => setColor(e.hex)}
|
|
||||||
colors={colors}
|
|
||||||
triangle="hide"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<button
|
|
||||||
class={classNames("container", {
|
|
||||||
amount: typeof props.amount !== "undefined",
|
|
||||||
disabled: props.disabled || props.amount === 0,
|
|
||||||
enabled: props.disabled === false || props.enabled,
|
|
||||||
})}
|
|
||||||
style={
|
|
||||||
typeof props.amount !== "undefined"
|
|
||||||
? {
|
|
||||||
"--amount": JSON.stringify(props.amount.toString()),
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
isColor() ? setActive((e) => !e) : props.callback && props.callback()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{typeof props.icon === "string" ? (
|
|
||||||
<img
|
|
||||||
src={`/assets/${props.icon}.png`}
|
|
||||||
alt={`${props.icon}.png`}
|
|
||||||
class="pixelart"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={props.icon}
|
|
||||||
color={props.iconColor ?? "#444"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<span>{props.text}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Item
|
|
|
@ -1,50 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { For } from "solid-js"
|
|
||||||
import { fieldIndex } from "~/lib/utils/helpers"
|
|
||||||
import { Field } from "../../interfaces/frontend"
|
|
||||||
import { count } from "./Gamefield"
|
|
||||||
|
|
||||||
function Labeling() {
|
|
||||||
let elems: (Field & {
|
|
||||||
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 (
|
|
||||||
<For each={elems}>
|
|
||||||
{(props) => (
|
|
||||||
<span
|
|
||||||
class={classNames("label", props.orientation, props.field)}
|
|
||||||
style={{ "--x": props.x, "--y": props.y }}
|
|
||||||
>
|
|
||||||
{props.field}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Labeling
|
|
|
@ -1,71 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { createEffect } from "solid-js"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import { ShipProps } from "../../interfaces/frontend"
|
|
||||||
|
|
||||||
const sizes: { [n: number]: number } = {
|
|
||||||
2: 96,
|
|
||||||
3: 144,
|
|
||||||
4: 196,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Ship(
|
|
||||||
props: ShipProps & {
|
|
||||||
preview?: boolean
|
|
||||||
warn?: boolean
|
|
||||||
color?: string
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const { selfIndex } = useSession()
|
|
||||||
const filename = () =>
|
|
||||||
`ship_${selfIndex()?.i === 1 ? "red" : "blue"}_${props.size}x_${
|
|
||||||
props.variant
|
|
||||||
}.gif`
|
|
||||||
let canvasRef: HTMLCanvasElement
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const canvas = canvasRef
|
|
||||||
const ctx = canvas?.getContext("2d")
|
|
||||||
if (!canvas || !ctx) return
|
|
||||||
const gif = new Image()
|
|
||||||
gif.src = "/assets/" + filename()
|
|
||||||
|
|
||||||
// Load the GIF and start rendering
|
|
||||||
gif.onload = function () {
|
|
||||||
// Set the canvas size to match the GIF dimensions
|
|
||||||
canvas.width = props.orientation === "h" ? sizes[props.size] : 48
|
|
||||||
canvas.height = props.orientation === "v" ? sizes[props.size] : 48
|
|
||||||
|
|
||||||
if (props.orientation === "v")
|
|
||||||
// Rotate the canvas by 90 degrees
|
|
||||||
ctx.rotate((90 * Math.PI) / 180)
|
|
||||||
|
|
||||||
// Draw the rotated GIF
|
|
||||||
ctx.drawImage(
|
|
||||||
gif,
|
|
||||||
0,
|
|
||||||
props.orientation === "h" ? 0 : -48,
|
|
||||||
sizes[props.size],
|
|
||||||
48,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classNames("ship", "s" + props.size, props.orientation, {
|
|
||||||
preview: props.preview,
|
|
||||||
warn: props.warn,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
"--x": props.x,
|
|
||||||
"--y": props.y,
|
|
||||||
"--color": props.color ?? "limegreen",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<canvas ref={canvasRef!} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Ship
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { For, Show } from "solid-js"
|
|
||||||
import { gameProps } from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import Ship from "./Ship"
|
|
||||||
|
|
||||||
function Ships() {
|
|
||||||
const { selfIsActiveIndex, selfUser } = useSession()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Show when={gameProps.gameState !== "running" || !selfIsActiveIndex()}>
|
|
||||||
<For each={selfUser()?.ships}>{(props) => <Ship {...props} />}</For>
|
|
||||||
</Show>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Ships
|
|
|
@ -1,81 +0,0 @@
|
||||||
import { For, Match, Switch } from "solid-js"
|
|
||||||
import { gameProps, target, targetPreview } from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import {
|
|
||||||
compiledHits,
|
|
||||||
composeTargetTiles,
|
|
||||||
intersectingShip,
|
|
||||||
shipProps,
|
|
||||||
} from "~/lib/utils/helpers"
|
|
||||||
import GamefieldPointer from "./GamefieldPointer"
|
|
||||||
import HitElems from "./HitElems"
|
|
||||||
import Ship from "./Ship"
|
|
||||||
|
|
||||||
function Targets() {
|
|
||||||
const { activeIndex, ships } = useSession()
|
|
||||||
|
|
||||||
const ship = () => shipProps(ships(), gameProps.mode, targetPreview())
|
|
||||||
const intersectionProps = () => intersectingShip(ships(), ship())
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Match when={gameProps.gameState === "running"}>
|
|
||||||
<For
|
|
||||||
each={composeTargetTiles(
|
|
||||||
target(),
|
|
||||||
gameProps.mode,
|
|
||||||
compiledHits(activeIndex()),
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{(props) => <GamefieldPointer {...props} />}
|
|
||||||
</For>
|
|
||||||
<For
|
|
||||||
each={composeTargetTiles(
|
|
||||||
targetPreview(),
|
|
||||||
gameProps.mode,
|
|
||||||
compiledHits(activeIndex()),
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{(props) => <GamefieldPointer {...props} preview />}
|
|
||||||
</For>
|
|
||||||
</Match>
|
|
||||||
<Match
|
|
||||||
when={
|
|
||||||
gameProps.gameState === "starting" &&
|
|
||||||
gameProps.mode >= 0 &&
|
|
||||||
targetPreview().show
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Ship
|
|
||||||
{...ship()}
|
|
||||||
preview
|
|
||||||
warn={intersectionProps().score > 0}
|
|
||||||
color={
|
|
||||||
intersectionProps().fields.length
|
|
||||||
? "red"
|
|
||||||
: intersectionProps().borders.length
|
|
||||||
? "orange"
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<HitElems
|
|
||||||
hits={intersectionProps().fields.map((e, i) => ({
|
|
||||||
...e,
|
|
||||||
i,
|
|
||||||
hit: true,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
<HitElems
|
|
||||||
hits={intersectionProps().borders.map((e, i) => ({
|
|
||||||
...e,
|
|
||||||
i,
|
|
||||||
hit: true,
|
|
||||||
}))}
|
|
||||||
colorOverride={"orange"}
|
|
||||||
/>
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Targets
|
|
|
@ -1,99 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { For, createEffect, createSignal, onCleanup } from "solid-js"
|
|
||||||
|
|
||||||
function Grid() {
|
|
||||||
function floorClient(number: number) {
|
|
||||||
return Math.floor(number / 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [columns, setColumns] = createSignal(0)
|
|
||||||
const [rows, setRows] = createSignal(0)
|
|
||||||
const quantity = () => columns() * rows()
|
|
||||||
const [position, setPosition] = createSignal([0, 0])
|
|
||||||
const [active, setActve] = createSignal(false)
|
|
||||||
const [count, setCount] = createSignal(0)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
function handleResize() {
|
|
||||||
setColumns(floorClient(document.body.clientWidth))
|
|
||||||
setRows(floorClient(document.body.clientHeight))
|
|
||||||
}
|
|
||||||
handleResize()
|
|
||||||
window.addEventListener("resize", handleResize)
|
|
||||||
onCleanup(() => removeEventListener("resize", handleResize))
|
|
||||||
})
|
|
||||||
|
|
||||||
function Tile(props: { index: number }) {
|
|
||||||
const x = () => props.index % columns()
|
|
||||||
const y = () => Math.floor(props.index / columns())
|
|
||||||
const xDiff = () => (x() - position()[0]) / 20
|
|
||||||
const yDiff = () => (y() - position()[1]) / 20
|
|
||||||
const pos = () =>
|
|
||||||
Math.sqrt(xDiff() * xDiff() + yDiff() * yDiff()).toFixed(2)
|
|
||||||
|
|
||||||
function doEffect(posX: number, posY: number) {
|
|
||||||
if (active()) return
|
|
||||||
setPosition([posX, posY])
|
|
||||||
setActve(true)
|
|
||||||
|
|
||||||
function xDiff(x: number) {
|
|
||||||
return (x - posX) / 20
|
|
||||||
}
|
|
||||||
function yDiff(y: number) {
|
|
||||||
return (y - posY) / 20
|
|
||||||
}
|
|
||||||
function pos(x: number, y: number) {
|
|
||||||
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
|
|
||||||
}
|
|
||||||
const diagonals = [
|
|
||||||
pos(0, 0),
|
|
||||||
pos(columns(), 0),
|
|
||||||
pos(0, rows()),
|
|
||||||
pos(columns(), rows()),
|
|
||||||
]
|
|
||||||
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
setActve(false)
|
|
||||||
setCount((e) => e + 1)
|
|
||||||
},
|
|
||||||
Math.max(...diagonals) * 1000 + 300,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classNames({ tile: true, active: active() })}
|
|
||||||
style={{ "--delay": pos() + "s" }}
|
|
||||||
onClick={() => doEffect(x(), y())}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)",
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="tiles"
|
|
||||||
style={{
|
|
||||||
"--columns": columns(),
|
|
||||||
"--rows": rows(),
|
|
||||||
"--bg-color-1": colors[count() % colors.length],
|
|
||||||
"--bg-color-2": colors[(count() + 1) % colors.length],
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<For each={Array.from(Array(quantity()))}>
|
|
||||||
{(_tile, i) => <Tile index={i()} />}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Grid
|
|
|
@ -1,103 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { For, createEffect, createSignal, onCleanup } from "solid-js"
|
|
||||||
|
|
||||||
function Grid2() {
|
|
||||||
function floorClient(number: number) {
|
|
||||||
return Math.floor(number / 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
const [columns, setColumns] = createSignal(0)
|
|
||||||
const [rows, setRows] = createSignal(0)
|
|
||||||
const quantity = () => columns() * rows()
|
|
||||||
const [position, setPosition] = createSignal([0, 0])
|
|
||||||
const [active, setActve] = createSignal(false)
|
|
||||||
const [action, setAction] = createSignal(false)
|
|
||||||
const [count, setCount] = createSignal(0)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
function handleResize() {
|
|
||||||
setColumns(floorClient(document.body.clientWidth))
|
|
||||||
setRows(floorClient(document.body.clientHeight))
|
|
||||||
}
|
|
||||||
handleResize()
|
|
||||||
window.addEventListener("resize", handleResize)
|
|
||||||
onCleanup(() => removeEventListener("resize", handleResize))
|
|
||||||
})
|
|
||||||
|
|
||||||
const sentences = [
|
|
||||||
"Ethem ...",
|
|
||||||
"hat ...",
|
|
||||||
"lange ...",
|
|
||||||
"Hörner 🐂",
|
|
||||||
"Grüße von Mallorca 🌊 🦦 ☀️",
|
|
||||||
]
|
|
||||||
|
|
||||||
function Tile(props: { index: number }) {
|
|
||||||
const x = () => props.index % columns()
|
|
||||||
const y = () => Math.floor(props.index / columns())
|
|
||||||
const xDiff = () => (x() - position()[0]) / 20
|
|
||||||
const yDiff = () => (y() - position()[1]) / 20
|
|
||||||
const pos = () =>
|
|
||||||
Math.sqrt(xDiff() * xDiff() + yDiff() * yDiff()).toFixed(2)
|
|
||||||
|
|
||||||
function doEffect(posX: number, posY: number) {
|
|
||||||
if (action()) return
|
|
||||||
setPosition([posX, posY])
|
|
||||||
setActve((e) => !e)
|
|
||||||
setAction(true)
|
|
||||||
|
|
||||||
function xDiff(x: number) {
|
|
||||||
return (x - posX) / 20
|
|
||||||
}
|
|
||||||
function yDiff(y: number) {
|
|
||||||
return (y - posY) / 20
|
|
||||||
}
|
|
||||||
function pos(x: number, y: number) {
|
|
||||||
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
|
|
||||||
}
|
|
||||||
const diagonals = [
|
|
||||||
pos(0, 0),
|
|
||||||
pos(columns(), 0),
|
|
||||||
pos(0, rows()),
|
|
||||||
pos(columns(), rows()),
|
|
||||||
]
|
|
||||||
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
setAction(false)
|
|
||||||
if (active()) setCount((e) => e + 1)
|
|
||||||
},
|
|
||||||
Math.max(...diagonals) * 1000 + 1000,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classNames("tile", active() ? "active" : "inactive")}
|
|
||||||
style={{ "--delay": pos() + "s" }}
|
|
||||||
onClick={() => doEffect(x(), y())}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="tiles"
|
|
||||||
style={{
|
|
||||||
"--columns": columns(),
|
|
||||||
"--rows": rows(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="center-div">
|
|
||||||
<h1 class={classNames("headline", !active ? "active" : "inactive")}>
|
|
||||||
{sentences[count() % sentences.length]}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<For each={Array.from(Array(quantity()))}>
|
|
||||||
{(_tile, i) => <Tile index={i()} />}
|
|
||||||
</For>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Grid2
|
|
|
@ -1,38 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
import { JSX } from "solid-js"
|
|
||||||
|
|
||||||
function Button(props: {
|
|
||||||
type: "red" | "orange" | "green" | "gray"
|
|
||||||
disabled?: boolean
|
|
||||||
onClick: () => void
|
|
||||||
children: JSX.Element
|
|
||||||
latching?: boolean
|
|
||||||
isLatched?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
disabled={props.disabled}
|
|
||||||
class={classNames(
|
|
||||||
"font-farro rounded-xl px-8 py-4 text-5xl font-medium duration-100",
|
|
||||||
props.disabled
|
|
||||||
? "border-4 border-dashed"
|
|
||||||
: props.latching
|
|
||||||
? props.isLatched
|
|
||||||
? "mx-1 my-0.5 border-t-4"
|
|
||||||
: "mx-1 my-0.5 border-b-4"
|
|
||||||
: "mx-1 my-0.5 border-b-4 active:border-b-0 active:border-t-4",
|
|
||||||
{
|
|
||||||
"border-red-600 bg-red-500": props.type === "red",
|
|
||||||
"border-orange-400 bg-warn": props.type === "orange",
|
|
||||||
"border-green-600 bg-green-500": props.type === "green",
|
|
||||||
"border-gray-600 bg-gray-500": props.type === "gray",
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
onClick={() => props.onClick()}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Button
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { JSX } from "solid-js"
|
|
||||||
|
|
||||||
function Icon(props: {
|
|
||||||
src: string
|
|
||||||
children: JSX.Element
|
|
||||||
onClick?: () => void
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
class="mx-4 mt-4 flex flex-col items-center border-none"
|
|
||||||
onClick={() => props.onClick && props.onClick()}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="pixelart mb-1 box-content w-16 rounded-xl bg-white p-1"
|
|
||||||
src={"/assets/" + props.src}
|
|
||||||
alt={props.src}
|
|
||||||
/>
|
|
||||||
<span class="font-semibold">{props.children}</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Icon
|
|
|
@ -1,139 +0,0 @@
|
||||||
import {
|
|
||||||
faRightFromBracket,
|
|
||||||
faSpinnerThird,
|
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { useNavigate } from "@solidjs/router"
|
|
||||||
import { JSX, Show, createEffect, createSignal, onCleanup } from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { full, gameProps, leave, reset, users } from "~/hooks/useGameProps"
|
|
||||||
import { useSession } from "~/hooks/useSession"
|
|
||||||
import useSocket from "~/hooks/useSocket"
|
|
||||||
import { socket } from "~/lib/socket"
|
|
||||||
import Button from "./Button"
|
|
||||||
import Icon from "./Icon"
|
|
||||||
import Player from "./Player"
|
|
||||||
|
|
||||||
function WithDots(props: { children: JSX.Element }) {
|
|
||||||
const [dots, setDots] = createSignal(1)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const interval = setInterval(() => setDots((e) => (e % 3) + 1), 1000)
|
|
||||||
onCleanup(() => clearInterval(interval))
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{props.children + " "}
|
|
||||||
{Array.from(Array(dots()), () => ".").join("")}
|
|
||||||
{Array.from(Array(3 - dots()), () => (
|
|
||||||
<> </>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function LobbyFrame(props: { openSettings: () => void }) {
|
|
||||||
const { isConnected } = useSocket()
|
|
||||||
const navigator = useNavigate()
|
|
||||||
const { session } = useSession()
|
|
||||||
const [launchTime, setLaunchTime] = createSignal(3)
|
|
||||||
const launching = () => users[0]?.isReady && users[1]?.isReady
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!launching() || launchTime() > 0) return
|
|
||||||
socket.emit("gameState", "starting")
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (!launching()) return setLaunchTime(3)
|
|
||||||
if (launchTime() < 0) return
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setLaunchTime((e) => e - 1)
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
onCleanup(() => clearTimeout(timeout))
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (gameProps.gameId || !isConnected) return
|
|
||||||
socket.emit("update", full)
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
if (gameProps.gameState === "unknown" || gameProps.gameState === "lobby")
|
|
||||||
return
|
|
||||||
navigator("/gamefield")
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="mx-32 flex flex-col self-stretch rounded-3xl bg-gray-400">
|
|
||||||
<div class="flex items-center justify-between border-b-2 border-slate-900">
|
|
||||||
<Icon src="speech_bubble.png">Chat</Icon>
|
|
||||||
<h1 class="font-farro text-5xl font-medium">
|
|
||||||
<Show
|
|
||||||
when={!launching()}
|
|
||||||
fallback={
|
|
||||||
<WithDots>
|
|
||||||
{launchTime() < 0
|
|
||||||
? "Game starts"
|
|
||||||
: "Game is starting in " + launchTime()}
|
|
||||||
</WithDots>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{"Game-PIN: "}
|
|
||||||
<Show
|
|
||||||
when={isConnected}
|
|
||||||
fallback={<FontAwesomeIcon icon={faSpinnerThird} spin />}
|
|
||||||
>
|
|
||||||
<span class="underline">{gameProps.gamePin ?? "----"}</span>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</h1>
|
|
||||||
<Icon src="gear.png" onClick={props.openSettings}>
|
|
||||||
Settings
|
|
||||||
</Icon>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-around">
|
|
||||||
<Show
|
|
||||||
when={isConnected}
|
|
||||||
fallback={
|
|
||||||
<p class="font-farro m-48 text-center text-6xl font-medium">
|
|
||||||
Warte auf Verbindung
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Player src="player_blue.png" i={0} userId={session()?.user?.id} />
|
|
||||||
<p class="font-farro m-4 text-6xl font-semibold">VS</p>
|
|
||||||
<Show
|
|
||||||
when={users[1]}
|
|
||||||
fallback={
|
|
||||||
<p class="font-farro w-96 text-center text-4xl font-medium">
|
|
||||||
<WithDots>Warte auf Spieler 2</WithDots>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Player src="player_red.png" i={1} userId={session()?.user?.id} />
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-around border-t-2 border-slate-900 p-4">
|
|
||||||
<Button
|
|
||||||
type={launching() ? "gray" : "red"}
|
|
||||||
disabled={launching()}
|
|
||||||
onClick={() => {
|
|
||||||
leave(async () => {
|
|
||||||
reset()
|
|
||||||
navigator("/")
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>LEAVE</span>
|
|
||||||
<FontAwesomeIcon icon={faRightFromBracket} class="ml-4 w-12" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LobbyFrame
|
|
|
@ -1,120 +0,0 @@
|
||||||
import {
|
|
||||||
faCheck,
|
|
||||||
faHandPointer,
|
|
||||||
faHourglass1,
|
|
||||||
faHourglass2,
|
|
||||||
faHourglass3,
|
|
||||||
faHourglassClock,
|
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import { faCaretDown } from "@fortawesome/sharp-solid-svg-icons"
|
|
||||||
import classNames from "classnames"
|
|
||||||
import { Show, createEffect, createSignal, onCleanup } from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { setIsReadyFor, users } from "~/hooks/useGameProps"
|
|
||||||
import { socket } from "~/lib/socket"
|
|
||||||
import Button from "./Button"
|
|
||||||
|
|
||||||
function HourGlass() {
|
|
||||||
const [count, setCount] = createSignal(3)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const interval = setInterval(() => setCount((e) => (e + 1) % 4), 1000)
|
|
||||||
onCleanup(() => clearInterval(interval))
|
|
||||||
})
|
|
||||||
|
|
||||||
const icon = () => {
|
|
||||||
switch (count()) {
|
|
||||||
case 0:
|
|
||||||
return faHourglass3
|
|
||||||
case 1:
|
|
||||||
return faHourglass1
|
|
||||||
case 2:
|
|
||||||
return faHourglass2
|
|
||||||
case 3:
|
|
||||||
return faHourglass3
|
|
||||||
default:
|
|
||||||
return faHourglassClock
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FontAwesomeIcon icon={icon()} class="ml-4 w-12" spin={count() === 0} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Player(props: { src: string; i: 0 | 1; userId?: string }) {
|
|
||||||
const player = () => users[props.i]
|
|
||||||
const primary = () => props.userId && props.userId === player()?.id
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="flex w-96 flex-col items-center gap-4 p-4">
|
|
||||||
<p
|
|
||||||
class={classNames(
|
|
||||||
"font-farro w-max text-5xl",
|
|
||||||
primary() ? "font-semibold" : "font-normal",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{player()?.name ?? "Spieler " + (props.i === 1 ? "2" : "1")}
|
|
||||||
</p>
|
|
||||||
<div class="relative">
|
|
||||||
<img
|
|
||||||
class="pixelart w-64"
|
|
||||||
src={"/assets/" + props.src}
|
|
||||||
alt={props.src}
|
|
||||||
/>
|
|
||||||
<Show when={primary()}>
|
|
||||||
<button class="absolute right-4 top-4 h-14 w-14 rounded-lg border-2 border-dashed border-warn bg-gray-800 bg-opacity-90">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
class="h-full w-full text-warn"
|
|
||||||
icon={faCaretDown}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
type={
|
|
||||||
player()?.isConnected
|
|
||||||
? users[props.i]?.isReady
|
|
||||||
? "green"
|
|
||||||
: "orange"
|
|
||||||
: "gray"
|
|
||||||
}
|
|
||||||
latching
|
|
||||||
isLatched={users[props.i]?.isReady}
|
|
||||||
onClick={() => {
|
|
||||||
if (!player()) return
|
|
||||||
socket.emit("isReady", !users[props.i]?.isReady)
|
|
||||||
setIsReadyFor({
|
|
||||||
i: props.i,
|
|
||||||
isReady: !users[props.i]?.isReady,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
disabled={!primary()}
|
|
||||||
>
|
|
||||||
Ready
|
|
||||||
{users[props.i]?.isReady && player()?.isConnected ? (
|
|
||||||
<FontAwesomeIcon icon={faCheck} class="ml-4 w-12" />
|
|
||||||
) : primary() ? (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faHandPointer}
|
|
||||||
class="ml-4 w-12"
|
|
||||||
style={{
|
|
||||||
"--fa-bounce-start-scale-x": 1.05,
|
|
||||||
"--fa-bounce-start-scale-y": 0.95,
|
|
||||||
"--fa-bounce-jump-scale-x": 0.95,
|
|
||||||
"--fa-bounce-jump-scale-y": 1.05,
|
|
||||||
"--fa-bounce-land-scale-x": 1.025,
|
|
||||||
"--fa-bounce-land-scale-y": 0.975,
|
|
||||||
"--fa-bounce-height": "-0.125em",
|
|
||||||
}}
|
|
||||||
bounce
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<HourGlass />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Player
|
|
|
@ -1,46 +0,0 @@
|
||||||
import {
|
|
||||||
faToggleLargeOff,
|
|
||||||
faToggleLargeOn,
|
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import classNames from "classnames"
|
|
||||||
import { JSX } from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { gameProps, setGameSetting } from "~/hooks/useGameProps"
|
|
||||||
import { GameSettingKeys } from "../../../interfaces/frontend"
|
|
||||||
|
|
||||||
function Setting(props: { children: JSX.Element; key: GameSettingKeys }) {
|
|
||||||
const state = () => gameProps[props.key]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label class="flex items-center justify-between" for={props.key}>
|
|
||||||
<span class="col-span-2 w-96 select-none text-5xl text-white drop-shadow-md">
|
|
||||||
{props.children}
|
|
||||||
</span>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
class={classNames(
|
|
||||||
"text-md mx-auto rounded-full px-4 drop-shadow-md transition-all",
|
|
||||||
state() ? "text-blue-500" : "text-gray-800",
|
|
||||||
{
|
|
||||||
"bg-gray-300 ": state(),
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
size="3x"
|
|
||||||
icon={state() ? faToggleLargeOn : faToggleLargeOff}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
class="bg-none"
|
|
||||||
checked={state()}
|
|
||||||
type="checkbox"
|
|
||||||
id={props.key}
|
|
||||||
onChange={() =>
|
|
||||||
setGameSetting({
|
|
||||||
[props.key]: !state(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
hidden={true}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Setting
|
|
|
@ -1,72 +0,0 @@
|
||||||
import { faRotateLeft } from "@fortawesome/pro-regular-svg-icons"
|
|
||||||
import { faXmark } from "@fortawesome/pro-solid-svg-icons"
|
|
||||||
import {} from "solid-js"
|
|
||||||
import { FontAwesomeIcon } from "~/components/FontAwesomeIcon"
|
|
||||||
import { full, setSetting } from "~/hooks/useGameProps"
|
|
||||||
import { socket } from "~/lib/socket"
|
|
||||||
import { GameSettings } from "../../../interfaces/frontend"
|
|
||||||
import Setting from "./Setting"
|
|
||||||
|
|
||||||
function Settings(props: { closeSettings: () => void }) {
|
|
||||||
const gameSetting = (newSettings: GameSettings) => {
|
|
||||||
const hash = setSetting(newSettings)
|
|
||||||
socket.emit("gameSetting", newSettings, (newHash) => {
|
|
||||||
if (newHash === hash) return
|
|
||||||
console.log("hash", hash, newHash)
|
|
||||||
socket.emit("update", full)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="absolute inset-0 flex flex-col items-center justify-center bg-black/40">
|
|
||||||
<div class="w-full max-w-screen-lg">
|
|
||||||
<div class="mx-16 flex flex-col rounded-3xl border-4 border-slate-800 bg-zinc-500 p-8">
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<h1 class="font-farro ml-auto pl-14 text-center text-6xl font-semibold text-white shadow-black drop-shadow-lg">
|
|
||||||
Settings
|
|
||||||
</h1>
|
|
||||||
<button
|
|
||||||
class="right-6 top-6 ml-auto h-14 w-14"
|
|
||||||
onClick={() => props.closeSettings()}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
class="h-full w-full text-gray-800 drop-shadow-md"
|
|
||||||
size="3x"
|
|
||||||
icon={faXmark}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="mt-8 rounded-xl bg-zinc-600 p-8">
|
|
||||||
<div class="flex items-center justify-end">
|
|
||||||
<button
|
|
||||||
class="right-12 top-8 h-14 w-14"
|
|
||||||
onClick={() =>
|
|
||||||
gameSetting({
|
|
||||||
allowSpectators: true,
|
|
||||||
allowSpecials: true,
|
|
||||||
allowChat: true,
|
|
||||||
allowMarkDraw: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
class="h-full w-full text-gray-800 drop-shadow-md"
|
|
||||||
size="3x"
|
|
||||||
icon={faRotateLeft}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-8">
|
|
||||||
<Setting key="allowSpectators">Erlaube Zuschauer</Setting>
|
|
||||||
<Setting key="allowSpecials">Erlaube spezial Items</Setting>
|
|
||||||
<Setting key="allowChat">Erlaube den Chat</Setting>
|
|
||||||
<Setting key="allowMarkDraw">Erlaube zeichen/makieren</Setting>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Settings
|
|
|
@ -1,81 +0,0 @@
|
||||||
import classNames from "classnames"
|
|
||||||
|
|
||||||
function Logo(props: { small?: boolean }) {
|
|
||||||
return (
|
|
||||||
<a href="/">
|
|
||||||
<div class="relative flex flex-col items-center rounded-sm border-x-4 border-y-2 border-shield-gray bg-shield-lightgray md:border-x-8 md:border-y-4">
|
|
||||||
<h1
|
|
||||||
class={classNames(
|
|
||||||
"font-checkpoint mx-16 my-2 flex flex-col gap-2 border-y-2 border-slate-700 text-center text-2xl leading-tight tracking-widest sm:mx-24 sm:my-3 sm:gap-3 sm:border-y-[3px] sm:text-4xl md:mx-36 md:my-4 md:gap-4 md:border-y-4 md:text-5xl",
|
|
||||||
{ "xl:gap-6 xl:py-2 xl:text-6xl": !props.small },
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span>Leaky</span>
|
|
||||||
<span>Ships</span>
|
|
||||||
</h1>
|
|
||||||
<Screws small={props.small} />
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Screws(props: { small?: boolean }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Screw
|
|
||||||
small={props.small}
|
|
||||||
orientation={classNames("top-1 left-1 sm:top-2 sm:left-2", {
|
|
||||||
"xl:top-4 xl:left-4": !props.small,
|
|
||||||
})}
|
|
||||||
rotation="rotate-[135deg]"
|
|
||||||
/>
|
|
||||||
<Screw
|
|
||||||
small={props.small}
|
|
||||||
orientation={classNames("top-1 right-1 sm:top-2 sm:right-2", {
|
|
||||||
"xl:top-4 xl:right-4": !props.small,
|
|
||||||
})}
|
|
||||||
rotation="rotate-[5deg]"
|
|
||||||
/>
|
|
||||||
<Screw
|
|
||||||
small={props.small}
|
|
||||||
orientation={classNames("bottom-1 right-1 sm:bottom-2 sm:right-2", {
|
|
||||||
"xl:bottom-4 xl:right-4": !props.small,
|
|
||||||
})}
|
|
||||||
rotation="rotate-[150deg]"
|
|
||||||
/>
|
|
||||||
<Screw
|
|
||||||
small={props.small}
|
|
||||||
orientation={classNames("bottom-1 left-1 sm:bottom-2 sm:left-2", {
|
|
||||||
"xl:bottom-4 xl:left-4": !props.small,
|
|
||||||
})}
|
|
||||||
rotation="rotate-[20deg]"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Screw(props: {
|
|
||||||
orientation: string
|
|
||||||
rotation: string
|
|
||||||
small?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={classNames(
|
|
||||||
"absolute flex h-3 w-3 flex-col items-center justify-center rounded-full border-[1px] border-neutral-700 bg-neutral-400 sm:h-5 sm:w-5 sm:border-2 md:h-6 md:w-6",
|
|
||||||
{ "xl:h-8 xl:w-8": !props.small },
|
|
||||||
props.orientation,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<hr
|
|
||||||
class={classNames(
|
|
||||||
"color w-full border-neutral-500 sm:border-t-2",
|
|
||||||
{ "xl:border-t-4": !props.small },
|
|
||||||
props.rotation,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Logo
|
|