main
Ashelyn Dawn 4 years ago
parent 01dcae094a
commit 64a0c4e0b5

@ -3,6 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link href="https://fonts.googleapis.com/css2?family=Major+Mono+Display&family=Share+Tech+Mono&family=Source+Code+Pro:wght@300&family=VT323&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta

@ -1,42 +1,18 @@
import React, {useRef, useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import ReactMarkdown from 'react-markdown' import useWindowSize from '../../hooks/useWindowSize'
import styles from './App.module.css'; import styles from './App.module.css';
import Text from '../Text/Text';
import backgroundURL from './background.png'
function App({onCommand, game}) { function App({onCommand, game}) {
const inputRef = useRef()
const playAreaRef = useRef()
const [state, setState] = useState({}) const [state, setState] = useState({})
const messages = state.messages || [] const messages = state.messages || []
function onSubmit(ev) { const {width, height} = useWindowSize()
if(ev) ev.preventDefault(); const scaleX = width / 600
const scaleY = height / 400
if(!inputRef.current?.value.trim()) // const scale = 0 || Math.min(scaleX, scaleY)
return;
if(inputRef.current){
onCommand(inputRef.current.value)
inputRef.current.value = ''
}
if(playAreaRef.current) {
const viewArea = playAreaRef.current.firstChild;
setImmediate(() => viewArea.scrollTop = viewArea.scrollHeight)
}
}
useEffect(() => {
if(!playAreaRef.current) return;
const playArea = playAreaRef.current
function onClick() {
inputRef.current.focus()
}
playArea.addEventListener('click', onClick)
return () => playArea.removeEventListener('click', onClick)
}, [])
useEffect(() => { useEffect(() => {
game.onChange(setState) game.onChange(setState)
@ -44,30 +20,11 @@ function App({onCommand, game}) {
game.saveDraft() game.saveDraft()
}, [game]) }, [game])
const {directions, ...printedState} = state
return ( return (
<div className={styles.app}> <div style={{transform: `scale(${scaleX}, ${scaleY})`, overflow: 'hidden'}} className={styles.screen}>
<div ref={playAreaRef} className={styles.playArea}> <Text messages={messages} handleCommand={onCommand}/>
<div className={styles.output}> <div className={styles.overlay}>
{messages.map((message, i) => { <img alt="" src={backgroundURL}/>
if(message.type === 'message')
return <ReactMarkdown key={i}>{message.message}</ReactMarkdown>
if(message.type === 'command')
return <p key={i} className={styles.command}>{message.command}</p>
return null
})}
</div>
<form className={styles.input} onSubmit={onSubmit}>
<input ref={inputRef}/>
</form>
</div>
<div className={styles.infoArea}>
<pre>
{JSON.stringify(printedState, jsonReplacer, 2)}
</pre>
</div> </div>
</div> </div>
); );
@ -75,18 +32,3 @@ function App({onCommand, game}) {
export default App; export default App;
function jsonReplacer(key, value) {
if(value instanceof Set)
return Array.from(value)
if(value instanceof Map)
return Array.from(value).reduce((obj, [key, value]) => {
obj[key] = value;
return obj;
}, {});
if(key === 'messages' && Array.isArray(value) && value.length)
return ['...']
return value
}

@ -1,72 +1,29 @@
.app { .screen {
min-height: 100vh;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
}
.playArea {
flex: 1;
display: flex;
flex-direction: column;
max-height: 100vh;
user-select: none;
}
.output {
overflow-y: auto;
padding: 16px;
padding-bottom: 0;
min-height: 16px;
}
.input {
padding: 16px;
padding-top: 0;
position: relative; position: relative;
left: -1px; width: 600px;
font-weight: bold; height: 400px;
background: black;
image-rendering: pixelated;
} }
.input input { .overlay {
background: transparent;
border: none;
box-shadow: none;
outline: none;
width: 100%;
text-indent: 16px;
font-family: inherit;
font-weight: bold;
font-size: 16px;
position: relative;
top: -1px;
left: -1px;
}
.input::before {
position: absolute; position: absolute;
left: 16px; top: 0;
} right: 0;
left: 0;
bottom: 0;
.infoArea { opacity: .4;
width: 300px; overflow: hidden;
overflow-x: hidden; pointer-events: none;
overflow-y: scroll; mix-blend-mode: screen;
max-height: 100vh;
}
.command {
font-weight: bold;
position: relative;
text-indent: 16px;
white-space: pre;
} }
.command::before { .overlay img {
position: absolute; width: 100%;
left: -16px; height: 100%;
} object-fit: cover;
filter: brightness(2.5);
.command::before, .input::before {
content: '> ';
font-weight: bold;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

@ -0,0 +1,30 @@
import React, {useLayoutEffect, useRef} from 'react'
import ReactMarkdown from 'react-markdown'
import styles from './Text.module.css'
export default function Text({messages, currentInput, currentScroll}) {
const outputRef = useRef()
useLayoutEffect(() => {
outputRef.current.scrollTop = currentScroll
}, [currentScroll])
return (
<div className={styles.reflectedArea}>
<div ref={outputRef} className={styles.output}>
{messages.map((message, i) => {
if(message.type === 'message')
return <ReactMarkdown key={i}>{message.message}</ReactMarkdown>
if(message.type === 'command')
return <p key={i} className={styles.command}>{message.command}</p>
return null
})}
</div>
<div className={styles.input}>
<input tabIndex="-1" value={currentInput}/>
</div>
</div>
)
}

@ -0,0 +1,64 @@
import React, {useState, useEffect, useRef} from 'react'
import ReactMarkdown from 'react-markdown'
import styles from './Text.module.css'
import Reflection from './ReflectedText'
export default function Text({messages, handleCommand}) {
const inputRef = useRef()
const outputRef = useRef()
const textRef = useRef()
const [currentInput, setCurrentInput] = useState('')
const [currentScroll, setCurrentScroll] = useState(0)
function onSubmit(ev) {
if(ev) ev.preventDefault()
if(!inputRef.current?.value.trim())
return;
if(inputRef.current){
handleCommand(inputRef.current.value)
inputRef.current.value = ''
setCurrentInput('')
}
if(outputRef.current) {
setImmediate(() => outputRef.current.scrollTop = outputRef.current.scrollHeight)
setCurrentScroll(outputRef.current.scrollHeight)
}
}
useEffect(() => {
const playArea = textRef.current
function onClick() {
inputRef.current.focus()
}
playArea.addEventListener('click', onClick)
return () => playArea.removeEventListener('click', onClick)
}, [])
return (
<>
<div ref={textRef} className={styles.playArea}>
<div ref={outputRef} onScroll={() => setCurrentScroll(outputRef.current?.scrollTop)} className={styles.output}>
{messages.map((message, i) => {
if(message.type === 'message')
return <ReactMarkdown key={i}>{message.message}</ReactMarkdown>
if(message.type === 'command')
return <p key={i} className={styles.command}>{message.command}</p>
return null
})}
</div>
<form className={styles.input} onSubmit={onSubmit}>
<input ref={inputRef} onChange={ev => setCurrentInput(ev.target.value)} id="gameInput"/>
</form>
</div>
<Reflection messages={messages} currentInput={currentInput} currentScroll={currentScroll}/>
</>
)
}

@ -0,0 +1,80 @@
.playArea {
flex: 1;
display: flex;
flex-direction: column;
max-height: 100%;
user-select: none;
}
.reflectedArea {
display: flex;
flex-direction: column;
position: absolute;
width: 100%;
height: 100%;
transform: scale(.14, .12);
margin-top: -3px;
pointer-events: none;
opacity: .2;
filter: blur(2px);
overflow: hidden;
}
.output {
overflow-y: auto;
padding: 16px;
padding-bottom: 0;
min-height: 16px;
}
.input {
padding: 16px;
padding-top: 0;
position: relative;
font-weight: bold;
}
.input input {
background: transparent;
border: none;
box-shadow: none;
outline: none;
width: 100%;
text-indent: 16px;
font-family: inherit;
font-weight: bold;
font-size: 16px;
position: relative;
top: -1px;
left: -1px;
color: inherit;
}
.input::before {
position: absolute;
left: 16px;
}
.command {
font-weight: bold;
position: relative;
text-indent: 16px;
white-space: pre;
}
.command::before {
position: absolute;
left: -16px;
}
.command::before, .input::before {
content: '> ';
font-weight: bold;
}
/**
* Shadow
*/
.playArea {
}

@ -158,13 +158,11 @@ export default class Game {
findObjectsInRoom(name : string | undefined) : Item [] { findObjectsInRoom(name : string | undefined) : Item [] {
let items : Item [] = [] let items : Item [] = []
console.log(items)
for(const item of this.getState().items.values()) for(const item of this.getState().items.values())
if(item.location === name) if(item.location === name)
items.push(item) items.push(item)
console.log(items)
return items; return items;
} }

@ -60,8 +60,6 @@ export default class Parser {
} }
handleError(invalidCommands: InvalidCommandDetails []) { handleError(invalidCommands: InvalidCommandDetails []) {
console.log(invalidCommands)
if(!invalidCommands.length){ if(!invalidCommands.length){
throw new Error("I'm unsure what you're trying to do") throw new Error("I'm unsure what you're trying to do")
} }
@ -73,7 +71,6 @@ export default class Parser {
} }
this.game.say(mostValid.reason) this.game.say(mostValid.reason)
console.log(mostValid)
} }
understand(name : string) : VerbBuilder { understand(name : string) : VerbBuilder {

@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';
export default function useWindowSize() {
const [windowSize, setWindowSize] = useState(getSize);
useEffect(() => {
function handleResize() {
setWindowSize(getSize());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
function getSize() {
return {
width: window.innerWidth,
height: window.innerHeight
};
}

@ -1,3 +1,16 @@
html, body { html, body {
margin: 0; margin: 0;
background: black;
color: white;
}
#root {
display: flex;
min-height: 100vh;
align-items: center;
justify-content: center;
background: black;
/* font-family: 'VT323', monospace; */
font-family: 'Share Tech Mono', monospace;
overflow: hidden;
} }

Loading…
Cancel
Save