Compare commits

..

50 commits
main ... xstate

Author SHA1 Message Date
87be301794
Adding xState 2023-01-16 14:57:58 +01:00
71eb36344c
Implemented unfinished Homepage 2023-01-16 10:28:24 +01:00
ef809ff548
Rename stuff 2023-01-16 10:19:40 +01:00
a87b41d6c2
Tiny general style compatibility 2023-01-14 16:28:40 +01:00
3d21464165
Big rework for mutliple different events 2023-01-14 16:28:03 +01:00
1c22ffb829
half radar implementation 2023-01-13 13:34:14 +01:00
f5e1fe7e85
Use fontawesome pro icons 2023-01-13 09:16:33 +01:00
078a21c876
Bugfix the last hardly broken Quickfix 2023-01-13 09:00:11 +01:00
6cc8a4517c
Implemented amount to Item component 2023-01-13 08:49:09 +01:00
b2fd8f56fc
Quick fix 2023-01-12 11:50:04 +01:00
a282e35119
Added and changed types 2023-01-12 11:40:29 +01:00
b2c72ebb0d
Change way of imply param 2023-01-12 09:59:57 +01:00
42e4af46e9
Add implied border 2023-01-12 09:59:39 +01:00
b7a321cff5
Hotfix 2023-01-11 21:13:46 +01:00
9243b04322
Use of classNames 2023-01-11 21:08:40 +01:00
ebbbae4b15
Working multiselect/multi trageting 2023-01-11 21:05:17 +01:00
71a43dfe5c
Add custom useGameEvent hook 2023-01-11 16:31:59 +01:00
e803832812
Rewrote targetPreview for pulsation effect 2023-01-09 01:13:10 +01:00
948a6deb4b
Aesthetic looking gamefield 2023-01-08 21:32:50 +01:00
e0d89cce6d
Adapt to assets 2023-01-07 13:53:58 +01:00
f2fd6ca268
Format document 2023-01-07 13:53:39 +01:00
0cc0c69d7e
Fix layout glitches 2023-01-07 13:50:35 +01:00
65832b9b40
Added more assets 2023-01-07 12:29:59 +01:00
7c1d39950b
Repalce img with Image tags 2023-01-06 21:30:31 +01:00
c7862b8fe7
Added game assets 2023-01-06 21:26:27 +01:00
efd7a26932
merged react app into nextjs app 2023-01-06 16:33:37 +01:00
da55e92ecd
Init NextJS app 2023-01-06 15:58:48 +01:00
9fb1147158
deleted backend 2023-01-06 15:57:01 +01:00
bfd9d46892
Add functionality to Socket.IO client 2022-10-28 22:21:55 +02:00
cb66141806
Add Socket.IO server 2022-10-28 22:21:37 +02:00
84a15794e6
Fix fog images pointer events 2022-10-27 19:15:44 +02:00
840ddab9bc
Add Socket.IO client 2022-10-26 00:15:33 +02:00
3d0bf2bfde
Quickfix 2022-10-26 00:13:20 +02:00
1e1003758d
Reveal grid effect finished 2022-10-24 23:56:11 +02:00
14bd854b91
Colorfull shockwave effect 2022-10-24 20:36:50 +02:00
e183af7dea
Adding copy of files for grid outline effect 2022-10-24 18:24:39 +02:00
7cf930ccf2
Better functioning grid effect with rainbow colors 2022-10-24 18:16:57 +02:00
75cb6d537f
Implement CSS grid click effect 2022-10-24 00:35:28 +02:00
65ca3d49ba
Added example sketch for HM-10 module 2022-09-25 15:04:30 +02:00
2d6cd813f9
Improved Bluetooth sketches and docs 2022-09-25 14:45:22 +02:00
3652589944
Using Bluetooth on mobile 2022-09-25 00:28:29 +02:00
039098d027
targetPreview missle fix
`targetPreview` did not show up when moving the mouse from an empty to a missle field and then back to the empty field.

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

Fixed this by adding the dependency `show` for early return caused by no `positionChange` which blocks the early return when `targetPreview` is not being `show`-n.
2022-08-16 17:47:53 +02:00
4e8de6d7e0
Manuel missle fire 2022-08-16 11:03:12 +02:00
2ebaccfb2d
Refactoring 2022-08-15 23:16:31 +02:00
83f187a7dc
Finished targetPreview
Reverted back to react logic instead of scss logic and improved performance by partially showing the preview icon instead of letting it follow the mouse directly.
2022-08-14 02:37:58 +02:00
c837e0df2f
Working experimental targetPreview 2022-08-12 19:38:47 +02:00
038b9fe856
New functional features and fog images 2022-08-10 23:25:34 +02:00
b949c46649
Move styles and add mixins 2022-08-10 23:06:41 +02:00
665889eea1
Working CSS grid 2022-08-09 03:06:15 +02:00
aronmal
316847a192
Update README.md 2022-08-07 01:50:55 +02:00
163 changed files with 7583 additions and 15308 deletions

View file

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

View file

@ -1,6 +1,5 @@
# leaky-ships
Battleship web app made with SolidJS using solid-start.
Battleship web app with react frontend and ASP.NET Core backend
## Bluetooth

View file

@ -1,11 +1,3 @@
{
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": ["solid"],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:solid/typescript"
]
"extends": "next/core-web-vitals"
}

View file

@ -1,35 +1,36 @@
/log
src/drizzle/migrations
dist
.vinxi
.output
.vercel
.netlify
netlify
# Environment
.env
.env*.local
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# testing
/coverage
# Temp
gitignore
# next.js
/.next/
/out/
# System Files
# production
/build
# misc
.DS_Store
Thumbs.db
*.pem
# playwright
/test-results/
/playwright-report/
/playwright/.cache/
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,6 +0,0 @@
.next
next.config.js
package.json
pnpm-lock.yaml
postcss.config.js
tailwind.config.js

View 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

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

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

View 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

View 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

View 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

View 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

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

View 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

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

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

View file

@ -0,0 +1,30 @@
import { io } from 'socket.io-client';
function SocketIO() {
const socket = io("ws://localhost:5001");
socket.on('test2', (warst) => {
console.log('Test2:', warst, socket.id)
})
socket.on("connect", () => {
console.log(socket.connected); // true
setTimeout(() => {
socket.emit('test', "warst")
socket.emit('test', "tsra")
socket.emit('test', "1234")
// socket.disconnect()
}, 1000)
});
socket.on("test", () => {
console.log("Got test1234"); // false
});
socket.on("disconnect", () => {
console.log(socket.connected); // false
});
return (
<div>SocketIO</div>
)
}
export default SocketIO

View file

@ -0,0 +1,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

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

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

View file

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

View file

@ -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")
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View file

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

View file

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

View 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

File diff suppressed because it is too large Load diff

View file

@ -1,80 +1,39 @@
{
"name": "leaky-ships",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\"",
"push": "drizzle-kit push:pg",
"test": "pnpm playwright test --ui",
"typecheck": "tsc --noEmit --checkJs false --skipLibCheck"
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"type": "module",
"dependencies": {
"@auth/core": "^0.27.0",
"@auth/drizzle-adapter": "^0.7.0",
"@auth/solid-start": "^0.6.1",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/pro-duotone-svg-icons": "^6.5.1",
"@fortawesome/pro-light-svg-icons": "^6.5.1",
"@fortawesome/pro-regular-svg-icons": "^6.5.1",
"@fortawesome/pro-solid-svg-icons": "^6.5.1",
"@fortawesome/pro-thin-svg-icons": "^6.5.1",
"@fortawesome/sharp-solid-svg-icons": "^6.5.1",
"@paralleldrive/cuid2": "^2.2.2",
"@solidjs/meta": "^0.29.3",
"@solidjs/router": "^0.12.4",
"@solidjs/start": "^0.5.9",
"classnames": "^2.5.1",
"colors": "^1.4.0",
"drizzle-orm": "^0.29.4",
"drizzle-zod": "^0.5.1",
"http-status": "^1.7.3",
"json-stable-stringify": "^1.1.1",
"lodash-es": "^4.17.21",
"nodemailer": "^6.9.10",
"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"
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/pro-duotone-svg-icons": "^6.2.1",
"@fortawesome/pro-light-svg-icons": "^6.2.1",
"@fortawesome/pro-regular-svg-icons": "^6.2.1",
"@fortawesome/pro-solid-svg-icons": "^6.2.1",
"@fortawesome/pro-thin-svg-icons": "^6.2.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@fortawesome/sharp-solid-svg-icons": "^6.2.1",
"@next/font": "13.1.1",
"@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"@xstate/react": "^3.0.2",
"classnames": "^2.3.2",
"eslint": "8.31.0",
"eslint-config-next": "13.1.1",
"next": "13.1.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"socket.io-client": "^4.5.4",
"typescript": "4.9.4",
"xstate": "^4.35.2"
},
"packageManager": "pnpm@8.7.4",
"devDependencies": {
"@playwright/test": "^1.41.2",
"@total-typescript/ts-reset": "^0.5.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"
}
"@types/web-bluetooth": "^0.0.16",
"sass": "^1.57.1"
}
}

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

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

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

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

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

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

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

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

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

View file

@ -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,
},
})

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View file

@ -1,7 +0,0 @@
module.exports = {
semi: false,
plugins: [
"prettier-plugin-organize-imports",
"prettier-plugin-tailwindcss", // MUST come last
],
}

View file

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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

View file

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View file

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

View file

@ -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項目
------------------------------
・フォント名
・作者名(=マルセ)
・弊サイト名(=よく飛ばない鳥)
・弊サイトURLhttp://marusexijaxs.web.fc2.com/
------------------------------
を明記していただけると、たいへんありがたいです。
以上
-----------------------------------------------------------------
改訂履歴
2015/09/12 明文化
-----------------------------------------------------------------
2010-2015 マルセ / プロジェクトU-Fo
よく飛ばない鳥 http://marusexijaxs.web.fc2.com/
作者Twitter: https://twitter.com/maruse_xijaxs

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

View file

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()), () => (
<>&nbsp;</>
))}
</>
)
}
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

View file

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

View file

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

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more