NERDDISCO commited on
Commit
6c2bcb4
•
1 Parent(s): 8ee7f03

feat: changed everything to create Canvas2D games

Browse files
README.md CHANGED
@@ -1,13 +1,9 @@
1
- <h1 align="center"><big>fail4</big></h1>
2
 
3
  <p align="center"><img src="assets/logo.png" alt="logo" width="200"/></p>
4
 
5
  [![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb)
6
 
7
- > This project is built to fail
8
- > (until it doesn't)
9
- > Restart until it works
10
-
11
  # Prompt-Driven WebUI for Creative Development
12
 
13
  Discover a web-based user interface designed for prompt-driven development, enabling users to create
 
1
+ <h1 align="center"><big>2DGameGPT</big></h1>
2
 
3
  <p align="center"><img src="assets/logo.png" alt="logo" width="200"/></p>
4
 
5
  [![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb)
6
 
 
 
 
 
7
  # Prompt-Driven WebUI for Creative Development
8
 
9
  Discover a web-based user interface designed for prompt-driven development, enabling users to create
package-lock.json CHANGED
The diff for this file is too large to render. See raw diff
 
package.json CHANGED
@@ -1,5 +1,5 @@
1
  {
2
- "name": "fail4",
3
  "version": "1.0.0",
4
  "description": "JavaScript Canvas2D | 100% Prompt Driven Development (GPT) ",
5
  "keywords": [
@@ -16,6 +16,12 @@
16
  "name": "Gregor Adams",
17
  "url": "https://github.com/pixelass/"
18
  },
 
 
 
 
 
 
19
  "scripts": {
20
  "dev": "next dev",
21
  "prepare": "husky install",
@@ -46,8 +52,10 @@
46
  "@monaco-editor/react": "4.5.0",
47
  "@mui/icons-material": "5.11.16",
48
  "@mui/material": "5.12.0",
 
49
  "@types/prettier": "^2.7.2",
50
  "axios": "1.3.5",
 
51
  "esdeka": "0.1.18",
52
  "eslint": "8.37.0",
53
  "eslint-config-next": "13.2.4",
 
1
  {
2
+ "name": "2dgamegpt",
3
  "version": "1.0.0",
4
  "description": "JavaScript Canvas2D | 100% Prompt Driven Development (GPT) ",
5
  "keywords": [
 
16
  "name": "Gregor Adams",
17
  "url": "https://github.com/pixelass/"
18
  },
19
+ "contributors": [
20
+ {
21
+ "name": "Tim Pietrusky",
22
+ "url": "https://github.com/TimPietrusky"
23
+ }
24
+ ],
25
  "scripts": {
26
  "dev": "next dev",
27
  "prepare": "husky install",
 
52
  "@monaco-editor/react": "4.5.0",
53
  "@mui/icons-material": "5.11.16",
54
  "@mui/material": "5.12.0",
55
+ "@tweenjs/tween.js": "^18.6.4",
56
  "@types/prettier": "^2.7.2",
57
  "axios": "1.3.5",
58
+ "codesandbox": "2.2.3",
59
  "esdeka": "0.1.18",
60
  "eslint": "8.37.0",
61
  "eslint-config-next": "13.2.4",
public/js/utils.js CHANGED
@@ -52,12 +52,12 @@ function handleTemplate(template) {
52
  Function("Template", `${template};`)();
53
  }
54
 
55
- subscribe("fail4", event => {
56
  const { action } = event.data;
57
  switch (action.type) {
58
  case "call":
59
  host.current = event.source;
60
- answer(event.source, "fail4");
61
  handleTemplate(action.payload.template);
62
  break;
63
  case "broadcast":
 
52
  Function("Template", `${template};`)();
53
  }
54
 
55
+ subscribe("2DGameGPT", event => {
56
  const { action } = event.data;
57
  switch (action.type) {
58
  case "call":
59
  host.current = event.source;
60
+ answer(event.source, "2DGameGPT");
61
  handleTemplate(action.payload.template);
62
  break;
63
  case "broadcast":
src/components/Codepen.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import IconButton from "@mui/material/IconButton";
2
+ import { wrappers } from "@/utils/share";
3
+ import Tooltip from "@mui/material/Tooltip";
4
+ import { CodepenIcon } from "@/components/CodepenIcon";
5
+ import { ShareProps } from "../pages";
6
+
7
+ export function Codepen({ title, content }: ShareProps) {
8
+ return (
9
+ <form action="https://codepen.io/pen/define" method="POST" target="_blank">
10
+ <input
11
+ type="hidden"
12
+ name="data"
13
+ value={JSON.stringify({
14
+ title,
15
+ js: wrappers.js(content),
16
+ html: wrappers.miniHtml(title),
17
+ css: wrappers.css(),
18
+ })}
19
+ />
20
+
21
+ <Tooltip title="Open in Codepen">
22
+ <IconButton color="primary" type="submit" aria-label="Codepen">
23
+ <CodepenIcon />
24
+ </IconButton>
25
+ </Tooltip>
26
+ </form>
27
+ );
28
+ }
src/components/CodepenIcon.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import SvgIcon, { SvgIconProps } from "@mui/material/SvgIcon";
2
+
3
+ export function CodepenIcon(props: SvgIconProps) {
4
+ return (
5
+ <SvgIcon {...props} viewBox="0 0 24 24">
6
+ <path
7
+ fill="currentColor"
8
+ d="M8.21 12L6.88 12.89V11.11L8.21 12M11.47 9.82V7.34L7.31 10.12L9.16 11.36L11.47 9.82M16.7 10.12L12.53 7.34V9.82L14.84 11.36L16.7 10.12M7.31 13.88L11.47 16.66V14.18L9.16 12.64L7.31 13.88M12.53 14.18V16.66L16.7 13.88L14.84 12.64L12.53 14.18M12 10.74L10.12 12L12 13.26L13.88 12L12 10.74M22 12C22 17.5 17.5 22 12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12M18.18 10.12C18.18 10.09 18.18 10.07 18.18 10.05L18.17 10L18.17 10L18.16 9.95C18.15 9.94 18.15 9.93 18.14 9.91L18.13 9.89L18.11 9.85L18.1 9.83L18.08 9.8L18.06 9.77L18.03 9.74L18 9.72L18 9.7L17.96 9.68L17.95 9.67L12.3 5.91C12.12 5.79 11.89 5.79 11.71 5.91L6.05 9.67L6.05 9.68L6 9.7C6 9.71 6 9.72 6 9.72L5.97 9.74L5.94 9.77L5.93 9.8L5.9 9.83L5.89 9.85L5.87 9.89L5.86 9.91L5.84 9.95L5.84 10L5.83 10L5.82 10.05C5.82 10.07 5.82 10.09 5.82 10.12V13.88C5.82 13.91 5.82 13.93 5.82 13.95L5.83 14L5.84 14L5.84 14.05C5.85 14.06 5.85 14.07 5.86 14.09L5.87 14.11L5.89 14.15L5.9 14.17L5.92 14.2L5.94 14.23C5.95 14.24 5.96 14.25 5.97 14.26L6 14.28L6 14.3L6.04 14.32L6.05 14.33L11.71 18.1C11.79 18.16 11.9 18.18 12 18.18C12.1 18.18 12.21 18.15 12.3 18.1L17.95 14.33L17.96 14.32L18 14.3L18 14.28L18.03 14.26L18.06 14.23L18.08 14.2L18.1 14.17L18.11 14.15L18.13 14.11L18.14 14.09L18.16 14.05L18.16 14L18.17 14L18.18 13.95C18.18 13.93 18.18 13.91 18.18 13.88V10.12M17.12 12.89V11.11L15.79 12L17.12 12.89Z"
9
+ />
10
+ </SvgIcon>
11
+ );
12
+ }
src/components/Codesandbox.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+ import CropSquareIcon from "@mui/icons-material/CropSquare";
3
+ import IconButton from "@mui/material/IconButton";
4
+ import Tooltip from "@mui/material/Tooltip";
5
+ import { ShareProps } from "../pages";
6
+
7
+ export function Codesandbox({ title, content }: ShareProps) {
8
+ return (
9
+ <Tooltip title="Open in Codesandbox">
10
+ <IconButton
11
+ color="primary"
12
+ aria-label="Codsandbox"
13
+ onClick={async () => {
14
+ const { data } = await axios.post<string>("/api/url/codesandbox", {
15
+ content,
16
+ title,
17
+ });
18
+ window.open(data, "_blank");
19
+ }}
20
+ >
21
+ <CropSquareIcon />
22
+ </IconButton>
23
+ </Tooltip>
24
+ );
25
+ }
src/components/EditTitle.tsx CHANGED
@@ -1,7 +1,7 @@
1
  import { useState } from "react";
2
  import Box from "@mui/material/Box";
3
  import InputBase from "@mui/material/InputBase";
4
- import { roboto } from "@/lib/theme";
5
  import IconButton from "@mui/material/IconButton";
6
  import SaveIcon from "@mui/icons-material/Save";
7
 
@@ -24,7 +24,7 @@ export function EditTitle({ value, onSave }: { value: string; onSave(value: stri
24
  sx={{
25
  width: "100%",
26
  fontSize: 16,
27
- input: { ...roboto.style, p: 0, lineHeight: 1.5 },
28
  }}
29
  onChange={event => {
30
  setText(event.target.value);
 
1
  import { useState } from "react";
2
  import Box from "@mui/material/Box";
3
  import InputBase from "@mui/material/InputBase";
4
+ import { poppins } from "@/lib/theme";
5
  import IconButton from "@mui/material/IconButton";
6
  import SaveIcon from "@mui/icons-material/Save";
7
 
 
24
  sx={{
25
  width: "100%",
26
  fontSize: 16,
27
+ input: { ...poppins.style, p: 0, lineHeight: 1.5 },
28
  }}
29
  onChange={event => {
30
  setText(event.target.value);
src/components/ExampleButton.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button, Typography } from "@mui/material";
2
+ import { MouseEventHandler } from "react";
3
+
4
+ interface ExampleButtonProps {
5
+ text: string;
6
+ title?: string;
7
+ displayLength?: number;
8
+ onClick?: (text: string) => void;
9
+ }
10
+
11
+ /**
12
+ *
13
+ * A button that hosts an example "text" that can be used as the input
14
+ * to anything to get an inspiration on how to get started.
15
+ *
16
+ * @param props ExampleButtonProps
17
+ * @returns
18
+ */
19
+ export default function ExampleButton(props: ExampleButtonProps) {
20
+ const { title, text, displayLength = 50, onClick } = props;
21
+
22
+ const displayText = text.slice(0, displayLength) + (text.length > displayLength ? "..." : "");
23
+
24
+ const handleClick: MouseEventHandler = event => {
25
+ event.preventDefault();
26
+
27
+ if (onClick) {
28
+ onClick(text);
29
+ }
30
+ };
31
+
32
+ return (
33
+ <Button onClick={handleClick} sx={{ textTransform: "none" }} variant="outlined">
34
+ <Typography>{title ?? displayText}</Typography>
35
+ </Button>
36
+ );
37
+ }
src/components/InfoMenu.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, MouseEvent } from "react";
2
+ import IconButton from "@mui/material/IconButton";
3
+ import Menu from "@mui/material/Menu";
4
+ import MenuItem from "@mui/material/MenuItem";
5
+ import InfoIcon from "@mui/icons-material/Info";
6
+ import { useRouter } from "next/router";
7
+
8
+ export function InfoMenu() {
9
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
10
+ const open = Boolean(anchorEl);
11
+ const handleClick = (event: MouseEvent<HTMLElement>) => {
12
+ setAnchorEl(event.currentTarget);
13
+ };
14
+ const router = useRouter();
15
+ const handleClose = () => {
16
+ setAnchorEl(null);
17
+ };
18
+
19
+ return (
20
+ <div>
21
+ <IconButton
22
+ aria-label="Info"
23
+ id="infoMenu-button"
24
+ aria-controls={open ? "infoMenu-menu" : undefined}
25
+ aria-expanded={open ? "true" : undefined}
26
+ aria-haspopup="true"
27
+ onClick={handleClick}
28
+ >
29
+ <InfoIcon />
30
+ </IconButton>
31
+ <Menu
32
+ id="infoMenu-menu"
33
+ MenuListProps={{
34
+ "aria-labelledby": "infoMenu-button",
35
+ }}
36
+ anchorEl={anchorEl}
37
+ open={open}
38
+ onClose={handleClose}
39
+ PaperProps={{
40
+ style: {
41
+ width: "20ch",
42
+ },
43
+ }}
44
+ >
45
+ <MenuItem
46
+ onClick={async () => {
47
+ await router.push("/legal/data-policy");
48
+ handleClose();
49
+ }}
50
+ >
51
+ Data Policy
52
+ </MenuItem>
53
+ <MenuItem
54
+ onClick={async () => {
55
+ await router.push("/legal/imprint");
56
+ handleClose();
57
+ }}
58
+ >
59
+ Imprint
60
+ </MenuItem>
61
+ <MenuItem
62
+ onClick={async () => {
63
+ await router.push("/legal/cookie-policy");
64
+ handleClose();
65
+ }}
66
+ >
67
+ Cookie Policy
68
+ </MenuItem>
69
+ </Menu>
70
+ </div>
71
+ );
72
+ }
src/components/SimpleSnackbar.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from "@mui/material/Button";
2
+ import Snackbar from "@mui/material/Snackbar";
3
+ import IconButton from "@mui/material/IconButton";
4
+ import CloseIcon from "@mui/icons-material/Close";
5
+ import { SyntheticEvent } from "react";
6
+ import { Alert, SnackbarContent } from "@mui/material";
7
+
8
+ interface SnackbarProps {
9
+ showError: boolean;
10
+ handleClose: (event: SyntheticEvent | Event, reason?: string) => void;
11
+ message: string;
12
+ }
13
+
14
+ export default function SimpleSnackbar({ showError, handleClose, message }: SnackbarProps) {
15
+ const action = (
16
+ <>
17
+ <Button color="secondary" size="small" onClick={handleClose}>
18
+ UNDO
19
+ </Button>
20
+ <IconButton size="small" aria-label="close" color="inherit" onClick={handleClose}>
21
+ <CloseIcon fontSize="small" />
22
+ </IconButton>
23
+ </>
24
+ );
25
+
26
+ return (
27
+ <Snackbar
28
+ open={showError}
29
+ autoHideDuration={6000}
30
+ onClose={handleClose}
31
+ action={action}
32
+ anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
33
+ >
34
+ <Alert severity="error">{message}</Alert>
35
+ </Snackbar>
36
+ );
37
+ }
src/constants/index.ts CHANGED
@@ -1,26 +1,16 @@
1
  export const base = {
2
- default: `/** CHANGELOG
3
- * v1.0.0. Set up canvas
4
- */
5
- const canvas = document.querySelector('canvas');
6
  const ctx = canvas.getContext('2d');
7
- /**
8
- * Always use this random function
9
- */
10
- function random() {
11
- return Math.random()
12
- }
13
- /**
14
- * The draw function is called every frame to update the canvas.
15
- * To change the drawing logic, modify the code inside this function.
16
- */
17
  function draw(){
18
- // TODO: Add drawing logic here
19
- // Set the desired FPS (frames per second) for the animation
20
  const FPS = 60;
21
- // Schedule the next frame to be drawn
 
 
 
22
  setTimeout(requestAnimationFrame(draw),1000/FPS)
23
  }
24
  draw();
25
- `.trim(),
26
  };
 
1
  export const base = {
2
+ default: `const canvas = document.querySelector('canvas');
3
+ canvas.style.backgroundColor = '#fff';
 
 
4
  const ctx = canvas.getContext('2d');
5
+
 
 
 
 
 
 
 
 
 
6
  function draw(){
 
 
7
  const FPS = 60;
8
+
9
+ // TODO: Add drawing logic here
10
+
11
+ // NEVER stop the loop
12
  setTimeout(requestAnimationFrame(draw),1000/FPS)
13
  }
14
  draw();
15
+ `.trim(),
16
  };
src/lib/theme.ts CHANGED
@@ -1,14 +1,13 @@
1
- import { Fira_Code, Roboto } from "next/font/google";
2
  import { experimental_extendTheme as extendTheme } from "@mui/material/styles";
3
 
4
- export const roboto = Roboto({
5
  weight: ["300", "400", "500", "700"],
6
  subsets: ["latin"],
7
  display: "swap",
8
  fallback: ["Helvetica", "Arial", "sans-serif"],
9
  });
10
 
11
- // Create a theme instance.
12
  const theme = extendTheme({
13
  colorSchemes: {
14
  light: {
@@ -33,7 +32,31 @@ const theme = extendTheme({
33
  },
34
  },
35
  typography: {
36
- ...roboto.style,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  },
38
  });
39
 
 
1
+ import { Fira_Code, Poppins } from "next/font/google";
2
  import { experimental_extendTheme as extendTheme } from "@mui/material/styles";
3
 
4
+ export const poppins = Poppins({
5
  weight: ["300", "400", "500", "700"],
6
  subsets: ["latin"],
7
  display: "swap",
8
  fallback: ["Helvetica", "Arial", "sans-serif"],
9
  });
10
 
 
11
  const theme = extendTheme({
12
  colorSchemes: {
13
  light: {
 
32
  },
33
  },
34
  typography: {
35
+ ...poppins.style,
36
+ h1: {
37
+ fontSize: "3em",
38
+ marginBottom: 24,
39
+ },
40
+ h2: {
41
+ fontSize: "1.9em",
42
+ marginBottom: 12,
43
+ },
44
+ h3: {
45
+ fontSize: "1.7em",
46
+ marginBottom: 12,
47
+ },
48
+ h4: {
49
+ fontSize: "1.25em",
50
+ marginBottom: 12,
51
+ },
52
+ h5: {
53
+ fontSize: "1em",
54
+ marginBottom: 12,
55
+ },
56
+ h6: {
57
+ fontSize: "0.8em",
58
+ marginBottom: 12,
59
+ },
60
  },
61
  });
62
 
src/pages/_document.tsx CHANGED
@@ -9,7 +9,7 @@ import Document, {
9
  } from "next/document";
10
  import createEmotionServer from "@emotion/server/create-instance";
11
  import { AppType } from "next/app";
12
- import { roboto } from "@/lib/theme";
13
  import createEmotionCache from "@/lib/createEmotionCache";
14
  import { MyAppProps } from "./_app";
15
  import { getInitColorSchemeScript } from "@mui/material/styles";
@@ -20,7 +20,7 @@ interface MyDocumentProps extends DocumentProps {
20
 
21
  export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
22
  return (
23
- <Html lang="en" className={roboto.className}>
24
  <Head>
25
  <link rel="shortcut icon" href="/favicon.ico" />
26
  <meta name="emotion-insertion-point" content="" />
 
9
  } from "next/document";
10
  import createEmotionServer from "@emotion/server/create-instance";
11
  import { AppType } from "next/app";
12
+ import { poppins } from "@/lib/theme";
13
  import createEmotionCache from "@/lib/createEmotionCache";
14
  import { MyAppProps } from "./_app";
15
  import { getInitColorSchemeScript } from "@mui/material/styles";
 
20
 
21
  export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
22
  return (
23
+ <Html lang="en" className={poppins.className}>
24
  <Head>
25
  <link rel="shortcut icon" href="/favicon.ico" />
26
  <meta name="emotion-insertion-point" content="" />
src/pages/api/{gpt.ts → generate.ts} RENAMED
@@ -9,7 +9,7 @@ export default async function handler(request: NextApiRequest, response: NextApi
9
  const answer = await toOpenAI(request.body);
10
  return response.status(200).json(answer);
11
  } catch (error) {
12
- return response.status((error as AxiosError).status ?? 500).json({});
13
  }
14
  default:
15
  return response.status(405).json({});
 
9
  const answer = await toOpenAI(request.body);
10
  return response.status(200).json(answer);
11
  } catch (error) {
12
+ return response.status((error as AxiosError).status ?? 500).json(error);
13
  }
14
  default:
15
  return response.status(405).json({});
src/pages/api/run.ts DELETED
@@ -1,18 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { NextApiRequest, NextApiResponse } from "next";
4
-
5
- const outputFile = path.join(__dirname, "../../../../project/src/index.js");
6
-
7
- export default async function handler(request: NextApiRequest, response: NextApiResponse) {
8
- switch (request.method) {
9
- case "POST":
10
- if (request.body.content) {
11
- await fs.writeFile(outputFile, request.body.content);
12
- return response.status(200).json({});
13
- }
14
- return response.status(500).json({});
15
- default:
16
- return response.status(405).json({});
17
- }
18
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/pages/api/url/codesandbox.ts ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getParameters } from "codesandbox/lib/api/define";
2
+ import { NextApiRequest, NextApiResponse } from "next";
3
+ import prettier from "prettier";
4
+ import parserHTML from "prettier/parser-html";
5
+ import parserCSS from "prettier/parser-postcss";
6
+ import parserBabel from "prettier/parser-babel";
7
+ import { wrappers } from "@/utils/share";
8
+
9
+ export default async function handler(request: NextApiRequest, response: NextApiResponse) {
10
+ const content = request.body.content as string;
11
+ const title = request.body.title as string;
12
+
13
+ const parameters = getParameters({
14
+ template: "static",
15
+ files: {
16
+ "index.html": {
17
+ content: prettier.format(wrappers.html(title), {
18
+ parser: "html",
19
+ plugins: [parserHTML],
20
+ }),
21
+ isBinary: false,
22
+ },
23
+ "style.css": {
24
+ content: prettier.format(wrappers.css(), {
25
+ parser: "css",
26
+ plugins: [parserCSS],
27
+ }),
28
+ isBinary: false,
29
+ },
30
+ "script.js": {
31
+ content: prettier.format(wrappers.js(content), {
32
+ parser: "babel",
33
+ plugins: [parserBabel],
34
+ }),
35
+ isBinary: false,
36
+ },
37
+ },
38
+ });
39
+
40
+ response
41
+ .status(200)
42
+ .json(`https://codesandbox.io/api/v1/sandboxes/define?parameters=${parameters}`);
43
+ }
src/pages/api/url/types.d.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ declare module "codesandbox-import-utils/lib/api/define" {
2
+ interface IFiles {
3
+ [key: string]: {
4
+ content: string | Record<string, unknown>;
5
+ isBinary: boolean;
6
+ };
7
+ }
8
+ }
src/pages/index.tsx CHANGED
@@ -1,6 +1,6 @@
1
- import { useEffect, useRef, useState } from "react";
2
 
3
- import axios from "axios";
4
  import AcUnitIcon from "@mui/icons-material/AcUnit";
5
  import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment";
6
  import CheckIcon from "@mui/icons-material/Check";
@@ -43,17 +43,29 @@ import FormControl from "@mui/material/FormControl";
43
  import InputLabel from "@mui/material/InputLabel";
44
  import Select from "@mui/material/Select";
45
  import MenuItem from "@mui/material/MenuItem";
46
- import { fontMono } from "@/lib/theme";
47
  import { useColorScheme } from "@mui/material/styles";
48
  import { getTheme, prettify } from "@/utils";
49
  import { answersAtom, showCodeAtom } from "@/store/atoms";
50
  import { base } from "@/constants";
51
  import { EditTitle } from "@/components/EditTitle";
52
-
 
 
 
 
 
 
 
53
  const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
54
 
 
 
 
 
 
55
  export default function Home() {
56
  const ref = useRef<HTMLIFrameElement>(null);
 
57
  const [template, setTemplate] = useState(prettify(base.default));
58
  const [runningId, setRunningId] = useState("1");
59
  const [activeId, setActiveId] = useState("1");
@@ -62,9 +74,12 @@ export default function Home() {
62
  const [showCode, setShowCode] = useAtom(showCodeAtom);
63
  const [loading, setLoading] = useState(false);
64
  const [loadingLive, setLoadingLive] = useState(true);
 
 
 
65
  const { mode, systemMode } = useColorScheme();
66
 
67
- const { call, subscribe } = useHost(ref, "fail4");
68
 
69
  const connection = useRef(false);
70
  const [tries, setTries] = useState(1);
@@ -99,8 +114,6 @@ export default function Home() {
99
  case "answer":
100
  connection.current = true;
101
  setLoadingLive(false);
102
-
103
- console.log("connected");
104
  break;
105
  default:
106
  break;
@@ -117,6 +130,14 @@ export default function Home() {
117
 
118
  const current = answers.find(({ id }) => id === activeId);
119
 
 
 
 
 
 
 
 
 
120
  function reload() {
121
  connection.current = false;
122
  if (ref.current) {
@@ -135,67 +156,23 @@ export default function Home() {
135
  position: "absolute",
136
  inset: 0,
137
  overflow: "hidden",
138
- flexDirection: "row",
139
  height: "100%",
140
  }}
141
  >
142
- <Stack sx={{ width: "50%", flex: 1 }}>
 
 
 
 
 
 
 
143
  <AppBar position="static" elevation={0} color="default">
144
  <Toolbar>
145
- <Button
146
- form="gpt-form"
147
- type="submit"
148
- aria-label={loading ? "Loading" : "Run"}
149
- aria-disabled={loading}
150
- disabled={loading}
151
- startIcon={
152
- loading ? <CircularProgress size={20} /> : <PlayArrowIcon />
153
- }
154
- >
155
- Run
156
- </Button>
157
-
158
- {current?.id === editingId ? (
159
- <EditTitle
160
- value={current.task}
161
- onSave={value => {
162
- setEditingId(null);
163
- setAnswers(previousAnswers =>
164
- previousAnswers.map(answer_ =>
165
- current.id === answer_.id
166
- ? {
167
- ...answer_,
168
- task: value,
169
- }
170
- : answer_
171
- )
172
- );
173
- }}
174
- />
175
- ) : (
176
- <>
177
- <Typography
178
- sx={{
179
- flex: 1,
180
- pl: 3,
181
- overflow: "hidden",
182
- textOverflow: "ellipsis",
183
- whiteSpace: "nowrap",
184
- }}
185
- >
186
- {current?.task}
187
- </Typography>
188
- <IconButton
189
- onClick={() => {
190
- if (current) {
191
- setEditingId(current.id);
192
- }
193
- }}
194
- >
195
- <EditIcon />
196
- </IconButton>
197
- </>
198
- )}
199
 
200
  <IconButton
201
  color="inherit"
@@ -229,12 +206,12 @@ export default function Home() {
229
  event.preventDefault();
230
  setAnswers(previousAnswers =>
231
  previousAnswers.map(previousAnswer => {
232
- console.log(previousAnswer.id, activeId);
233
  return previousAnswer.id === activeId
234
  ? { ...previousAnswer, content: template }
235
  : previousAnswer;
236
  })
237
  );
 
238
  reload();
239
  }
240
  }}
@@ -247,25 +224,29 @@ export default function Home() {
247
  fontSize: 14,
248
  }}
249
  onChange={async value => {
250
- console.log(value);
251
  setTemplate(value ?? "");
252
  }}
253
  />
254
  </Box>
255
  )}
256
  <Stack
257
- sx={{ flex: 1, display: showCode ? "none" : undefined, overflow: "hidden" }}
 
 
 
 
258
  >
259
  <Box
260
  component="form"
261
  id="gpt-form"
 
262
  onSubmit={async event => {
263
  event.preventDefault();
264
  const formData = new FormData(event.target as HTMLFormElement);
265
  const formObject = Object.fromEntries(formData);
266
  try {
267
  setLoading(true);
268
- const { data } = await axios.post("/api/gpt", formObject);
269
  const answer = data;
270
  setAnswers(previousAnswers => [answer, ...previousAnswers]);
271
  setRunningId(answer.id);
@@ -273,6 +254,8 @@ export default function Home() {
273
  setTemplate(prettify(answer.content));
274
  reload();
275
  } catch (error) {
 
 
276
  console.error(error);
277
  } finally {
278
  setLoading(false);
@@ -281,191 +264,314 @@ export default function Home() {
281
  >
282
  <Paper variant="outlined" sx={{ p: 0 }}>
283
  <Stack sx={{ p: 2, gap: 2 }}>
284
- <TextField
285
- multiline
286
- fullWidth
287
- id="prompt"
288
- name="prompt"
289
- label="Prompt"
290
- placeholder="matrix code"
291
- defaultValue="matrix code"
292
- maxRows={6}
293
- InputProps={{
294
- style: fontMono.style,
295
- }}
296
- />
297
- <TextField
298
- multiline
299
- fullWidth
300
- id="negativePrompt"
301
- name="negativePrompt"
302
- label="Negative Prompt"
303
- placeholder="images, audio files"
304
- maxRows={6}
305
- InputProps={{
306
- style: fontMono.style,
307
- }}
308
- />
309
- </Stack>
310
- </Paper>
311
- <Accordion disableGutters square elevation={0}>
312
- <AccordionSummary
313
- expandIcon={<ExpandMoreIcon />}
314
- aria-controls="gtp-options-content"
315
- id="gtp-options-header"
316
- sx={{
317
- bgcolor: "background.paper",
318
- color: "text.primary",
319
- }}
320
- >
321
- <Typography>Options</Typography>
322
- </AccordionSummary>
323
- <AccordionDetails>
324
- <FormControl fullWidth variant="outlined" sx={{ mb: 3 }}>
325
- <InputLabel id="gpt-model-select-label">Model</InputLabel>
326
- <Select
327
- labelId="gpt-model-select-label"
328
- id="gpt-model-select"
329
- name="model"
330
- defaultValue="gpt-3.5-turbo"
331
- label="Model"
332
- >
333
- <MenuItem value="gpt-3.5-turbo">GPT 3.5 turbo</MenuItem>
334
- <MenuItem value="gpt-4">GPT 4</MenuItem>
335
- </Select>
336
- </FormControl>
337
- <Stack
338
- spacing={2}
339
- direction="row"
340
- sx={{ mb: 2 }}
341
- alignItems="center"
342
- >
343
- <AcUnitIcon />
344
- <Slider
345
- marks
346
- id="temperature"
347
- name="temperature"
348
- min={0}
349
- max={0.8}
350
- defaultValue={0.2}
351
- step={0.1}
352
- valueLabelDisplay="auto"
353
- aria-label="Temperature"
354
  />
355
- <LocalFireDepartmentIcon />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  </Stack>
357
- <Stack
358
- spacing={2}
359
- direction="row"
360
- sx={{ mb: 2 }}
361
- alignItems="center"
362
- >
363
- <TollIcon />
364
- <Slider
365
- marks
366
- id="maxTokens"
367
- name="maxTokens"
368
- min={1024}
369
- max={4096}
370
- defaultValue={2048}
371
- step={256}
372
- valueLabelDisplay="auto"
373
- aria-label="Max Tokens"
 
 
 
 
 
 
 
374
  />
375
- <MoneyIcon />
376
  </Stack>
377
- <input
378
- id="template"
379
- name="template"
380
- type="hidden"
381
- value={template}
382
- onChange={event => {
383
- setTemplate(event.target.value);
 
 
 
 
 
384
  }}
385
- />
386
- </AccordionDetails>
387
- </Accordion>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  </Box>
389
 
390
- <List sx={{ flex: 1, overflow: "auto" }}>
391
- {answers.map((answer, index) => {
392
- return (
393
- <ListItem
394
- key={answer.id}
395
- secondaryAction={
396
- <Stack sx={{ flexDirection: "row", gap: 1 }}>
397
- {answer.id === "1" ? undefined : (
398
- <IconButton
399
- edge="end"
400
- aria-label="Delete"
401
- onClick={() => {
402
- setAnswers(previousAnswers =>
403
- previousAnswers.filter(
404
- ({ id }) => id !== answer.id
405
- )
406
- );
407
- if (runningId === answer.id) {
408
- const previous = answers[index + 1];
409
- if (previous) {
410
- setActiveId(previous.id);
411
- setRunningId(previous.id);
412
- setTemplate(
413
- prettify(previous.task)
414
- );
415
- reload();
 
 
 
 
 
 
 
 
 
 
416
  }
417
- }
418
- }}
419
- >
420
- <DeleteForeverIcon />
421
- </IconButton>
422
- )}
423
- </Stack>
424
- }
425
- disablePadding
426
- >
427
- <ListItemButton
428
- dense
429
- selected={activeId === answer.id}
430
- disabled={activeId === answer.id}
431
- role={undefined}
432
- onClick={() => {
433
- setActiveId(answer.id);
434
- setRunningId(answer.id);
435
- setTemplate(prettify(answer.content));
436
- reload();
437
- }}
438
  >
439
- <ListItemIcon>
440
- {runningId === answer.id ? (
441
- <CheckIcon />
442
- ) : (
443
- <VisibilityIcon />
444
- )}
445
- </ListItemIcon>
446
-
447
- <ListItemText
448
- primary={answer.task}
449
- primaryTypographyProps={{
450
- sx: {
451
- overflow: "hidden",
452
- textOverflow: "ellipsis",
453
- whiteSpace: "nowrap",
454
- fontSize: 16,
455
- },
456
  }}
457
- />
458
- </ListItemButton>
459
- </ListItem>
460
- );
461
- })}
462
- </List>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  </Stack>
464
  </Stack>
465
- <Stack sx={{ flex: 1, width: "50%", position: "relative" }}>
 
 
 
 
 
 
 
466
  <AppBar position="static" elevation={0} color="default">
467
  <Toolbar>
468
  <IconButton
 
469
  color="inherit"
470
  aria-label="Reload"
471
  onClick={() => {
@@ -474,6 +580,28 @@ export default function Home() {
474
  >
475
  <ReplayIcon />
476
  </IconButton>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  </Toolbar>
478
  </AppBar>
479
  {loadingLive && (
@@ -500,10 +628,24 @@ export default function Home() {
500
  overflow: "hidden",
501
  visibility: loadingLive ? "hidden" : undefined,
502
  }}
 
 
 
 
 
 
 
 
503
  src="/live"
504
  />
505
  </Stack>
506
  </Stack>
 
 
 
 
 
 
507
  </>
508
  );
509
  }
 
1
+ import { SyntheticEvent, useEffect, useRef, useState } from "react";
2
 
3
+ import axios, { AxiosError, AxiosError } from "axios";
4
  import AcUnitIcon from "@mui/icons-material/AcUnit";
5
  import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment";
6
  import CheckIcon from "@mui/icons-material/Check";
 
43
  import InputLabel from "@mui/material/InputLabel";
44
  import Select from "@mui/material/Select";
45
  import MenuItem from "@mui/material/MenuItem";
 
46
  import { useColorScheme } from "@mui/material/styles";
47
  import { getTheme, prettify } from "@/utils";
48
  import { answersAtom, showCodeAtom } from "@/store/atoms";
49
  import { base } from "@/constants";
50
  import { EditTitle } from "@/components/EditTitle";
51
+ import Link from "next/link";
52
+ import { fontMono } from "@/lib/theme";
53
+ import { Codesandbox } from "@/components/Codesandbox";
54
+ import { Codepen } from "@/components/Codepen";
55
+ import { InfoMenu } from "@/components/InfoMenu";
56
+ import SimpleSnackbar from "@/components/SimpleSnackbar";
57
+ import ExampleButton from "@/components/ExampleButton";
58
+ import { ListSubheader } from "@mui/material";
59
  const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
60
 
61
+ export interface ShareProps {
62
+ title: string;
63
+ content: string;
64
+ }
65
+
66
  export default function Home() {
67
  const ref = useRef<HTMLIFrameElement>(null);
68
+ const [prompt, setPrompt] = useState("");
69
  const [template, setTemplate] = useState(prettify(base.default));
70
  const [runningId, setRunningId] = useState("1");
71
  const [activeId, setActiveId] = useState("1");
 
74
  const [showCode, setShowCode] = useAtom(showCodeAtom);
75
  const [loading, setLoading] = useState(false);
76
  const [loadingLive, setLoadingLive] = useState(true);
77
+ const [showError, setShowError] = useState(false);
78
+ const [errorMessage, setErrorMessage] = useState("");
79
+
80
  const { mode, systemMode } = useColorScheme();
81
 
82
+ const { call, subscribe } = useHost(ref, "2DGameGPT");
83
 
84
  const connection = useRef(false);
85
  const [tries, setTries] = useState(1);
 
114
  case "answer":
115
  connection.current = true;
116
  setLoadingLive(false);
 
 
117
  break;
118
  default:
119
  break;
 
130
 
131
  const current = answers.find(({ id }) => id === activeId);
132
 
133
+ const handleSnackbarClose = (event: SyntheticEvent | Event, reason?: string) => {
134
+ if (reason === "clickaway") {
135
+ return;
136
+ }
137
+
138
+ setShowError(false);
139
+ };
140
+
141
  function reload() {
142
  connection.current = false;
143
  if (ref.current) {
 
156
  position: "absolute",
157
  inset: 0,
158
  overflow: "hidden",
159
+ flexDirection: { md: "row" },
160
  height: "100%",
161
  }}
162
  >
163
+ <Stack
164
+ sx={{
165
+ width: { md: "50%" },
166
+ height: { xs: "50%", md: "100%" },
167
+ flex: 1,
168
+ overflow: "hidden",
169
+ }}
170
+ >
171
  <AppBar position="static" elevation={0} color="default">
172
  <Toolbar>
173
+ <Typography variant="h3" component="h1" sx={{ flex: 1, m: 0 }}>
174
+ 2D GameCreator-GPT
175
+ </Typography>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  <IconButton
178
  color="inherit"
 
206
  event.preventDefault();
207
  setAnswers(previousAnswers =>
208
  previousAnswers.map(previousAnswer => {
 
209
  return previousAnswer.id === activeId
210
  ? { ...previousAnswer, content: template }
211
  : previousAnswer;
212
  })
213
  );
214
+ setTemplate(previousState => prettify(previousState));
215
  reload();
216
  }
217
  }}
 
224
  fontSize: 14,
225
  }}
226
  onChange={async value => {
 
227
  setTemplate(value ?? "");
228
  }}
229
  />
230
  </Box>
231
  )}
232
  <Stack
233
+ sx={{
234
+ flex: 1,
235
+ display: showCode ? "none" : undefined,
236
+ overflow: "hidden",
237
+ }}
238
  >
239
  <Box
240
  component="form"
241
  id="gpt-form"
242
+ sx={{ p: 1 }}
243
  onSubmit={async event => {
244
  event.preventDefault();
245
  const formData = new FormData(event.target as HTMLFormElement);
246
  const formObject = Object.fromEntries(formData);
247
  try {
248
  setLoading(true);
249
+ const { data } = await axios.post("/api/generate", formObject);
250
  const answer = data;
251
  setAnswers(previousAnswers => [answer, ...previousAnswers]);
252
  setRunningId(answer.id);
 
254
  setTemplate(prettify(answer.content));
255
  reload();
256
  } catch (error) {
257
+ setShowError(true);
258
+ setErrorMessage((error as AxiosError).message);
259
  console.error(error);
260
  } finally {
261
  setLoading(false);
 
264
  >
265
  <Paper variant="outlined" sx={{ p: 0 }}>
266
  <Stack sx={{ p: 2, gap: 2 }}>
267
+ <Stack direction="row" spacing={1}>
268
+ <TextField
269
+ multiline
270
+ fullWidth
271
+ required
272
+ id="prompt"
273
+ name="prompt"
274
+ label="Prompt"
275
+ value={prompt}
276
+ onChange={e => setPrompt(e.target.value)}
277
+ minRows={5}
278
+ InputProps={{
279
+ style: fontMono.style,
280
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  />
282
+
283
+ <Stack spacing={1}>
284
+ <FormControl variant="outlined" sx={{ minWidth: 180 }}>
285
+ <InputLabel id="gpt-command-select-label">
286
+ Command
287
+ </InputLabel>
288
+ <Select
289
+ labelId="gpt-command-select-label"
290
+ id="gpt-command-select"
291
+ name="command"
292
+ defaultValue="CREATE_GAME"
293
+ label="Command"
294
+ >
295
+ <MenuItem value="CREATE_GAME">
296
+ create game
297
+ </MenuItem>
298
+ <MenuItem value="ADD_FEATURE">
299
+ add feature
300
+ </MenuItem>
301
+ <MenuItem value="REMOVE_FEATURE">
302
+ remove feature
303
+ </MenuItem>
304
+ <MenuItem value="UPDATE_FEATURE">
305
+ update feature
306
+ </MenuItem>
307
+ <MenuItem value="FIX_BUG">fix bug</MenuItem>
308
+ </Select>
309
+ </FormControl>
310
+
311
+ <Button
312
+ form="gpt-form"
313
+ type="submit"
314
+ variant="contained"
315
+ fullWidth
316
+ aria-label={loading ? "Loading" : "Run"}
317
+ aria-disabled={loading}
318
+ disabled={loading}
319
+ startIcon={
320
+ loading ? (
321
+ <CircularProgress size={20} />
322
+ ) : (
323
+ <PlayArrowIcon />
324
+ )
325
+ }
326
+ sx={{ pl: 5, pr: 5, flexGrow: 1, overflow: "auto" }}
327
+ >
328
+ <Typography sx={{ fontWeight: "500" }}>
329
+ Run
330
+ </Typography>
331
+ </Button>
332
+ </Stack>
333
  </Stack>
334
+
335
+ <Stack direction="row" spacing={1} alignItems="center">
336
+ <Typography>Examples</Typography>
337
+
338
+ <ExampleButton
339
+ title={"Space Invaders"}
340
+ text={"Retro Space Invaders"}
341
+ onClick={setPrompt}
342
+ />
343
+
344
+ <ExampleButton
345
+ title="Jump & Run"
346
+ text={
347
+ "Jump & Run. Player collects coins to enter the next level. Various platform heights. Gras platform covers the whole ground. Space key for jumping, arrow keys for movement."
348
+ }
349
+ onClick={setPrompt}
350
+ />
351
+
352
+ <ExampleButton
353
+ title="Flappy Bird"
354
+ text={
355
+ "Flappy Bird. Intro screen, start the game by pressing space key. Bird starts flying on the left center of the screen. Gradually falls slowly. Pressing the space key over and over lets the bird fly higher. Pipes move from the right of the screen to the left. Collision detection when the bird hits the ground or a pipe. When collision detected, then show intro screen. Player gets a point for each passed pipe without an collision. Score is shown in the top left while bird is flying. High score on intro screen."
356
+ }
357
+ onClick={setPrompt}
358
  />
 
359
  </Stack>
360
+ </Stack>
361
+ </Paper>
362
+
363
+ <Paper variant="outlined" sx={{ mt: 2 }}>
364
+ <Accordion disableGutters square elevation={0}>
365
+ <AccordionSummary
366
+ expandIcon={<ExpandMoreIcon />}
367
+ aria-controls="gtp-options-content"
368
+ id="gtp-options-header"
369
+ sx={{
370
+ bgcolor: "background.paper",
371
+ color: "text.primary",
372
  }}
373
+ >
374
+ <Typography>Options</Typography>
375
+ </AccordionSummary>
376
+ <AccordionDetails>
377
+ <Stack gap={2}>
378
+ <TextField
379
+ fullWidth
380
+ multiline
381
+ required={process.env.NODE_ENV === "production"}
382
+ id="openAPIKey"
383
+ name="openAPIKey"
384
+ label="OpenAI API Key"
385
+ minRows={1}
386
+ InputProps={{
387
+ style: fontMono.style,
388
+ }}
389
+ />
390
+ <TextField
391
+ multiline
392
+ fullWidth
393
+ id="negativePrompt"
394
+ name="negativePrompt"
395
+ label="Negative Prompt"
396
+ placeholder="images, audio files"
397
+ maxRows={6}
398
+ InputProps={{
399
+ style: fontMono.style,
400
+ }}
401
+ />
402
+ <FormControl
403
+ fullWidth
404
+ variant="outlined"
405
+ sx={{ mb: 3 }}
406
+ >
407
+ <InputLabel id="gpt-model-select-label">
408
+ Model
409
+ </InputLabel>
410
+ <Select
411
+ labelId="gpt-model-select-label"
412
+ id="gpt-model-select"
413
+ name="model"
414
+ defaultValue="gpt-3.5-turbo"
415
+ label="Model"
416
+ >
417
+ <MenuItem value="gpt-3.5-turbo">
418
+ GPT 3.5 turbo
419
+ </MenuItem>
420
+ <MenuItem value="gpt-4">GPT 4</MenuItem>
421
+ </Select>
422
+ </FormControl>
423
+ </Stack>
424
+ <Stack
425
+ spacing={2}
426
+ direction="row"
427
+ sx={{ mb: 2 }}
428
+ alignItems="center"
429
+ >
430
+ <AcUnitIcon />
431
+ <Slider
432
+ marks
433
+ id="temperature"
434
+ name="temperature"
435
+ min={0}
436
+ max={0.8}
437
+ defaultValue={0.2}
438
+ step={0.1}
439
+ valueLabelDisplay="auto"
440
+ aria-label="Temperature"
441
+ />
442
+ <LocalFireDepartmentIcon />
443
+ </Stack>
444
+ <Stack
445
+ spacing={2}
446
+ direction="row"
447
+ sx={{ mb: 2 }}
448
+ alignItems="center"
449
+ >
450
+ <TollIcon />
451
+ <Slider
452
+ marks
453
+ id="maxTokens"
454
+ name="maxTokens"
455
+ min={1024}
456
+ max={8000}
457
+ defaultValue={2048}
458
+ step={256}
459
+ valueLabelDisplay="auto"
460
+ aria-label="Max Tokens"
461
+ />
462
+ <MoneyIcon />
463
+ </Stack>
464
+ <input
465
+ id="template"
466
+ name="template"
467
+ type="hidden"
468
+ value={template}
469
+ onChange={event => {
470
+ setTemplate(event.target.value);
471
+ }}
472
+ />
473
+ </AccordionDetails>
474
+ </Accordion>
475
+ </Paper>
476
  </Box>
477
 
478
+ <Paper variant="elevation" sx={{ p: 1, overflow: "auto" }}>
479
+ <List sx={{ flex: 1, p: 0 }}>
480
+ <ListSubheader
481
+ sx={{ fontSize: "1em", fontWeight: "normal", color: "white" }}
482
+ >
483
+ Games
484
+ </ListSubheader>
485
+ {answers.map((answer, index) => {
486
+ return (
487
+ <ListItem
488
+ key={answer.id}
489
+ secondaryAction={
490
+ <Stack sx={{ flexDirection: "row", gap: 1 }}>
491
+ {answer.id === "1" ? undefined : (
492
+ <IconButton
493
+ edge="end"
494
+ aria-label="Delete"
495
+ onClick={() => {
496
+ setAnswers(previousAnswers =>
497
+ previousAnswers.filter(
498
+ ({ id }) => id !== answer.id
499
+ )
500
+ );
501
+ if (runningId === answer.id) {
502
+ const previous =
503
+ answers[index + 1];
504
+ if (previous) {
505
+ setActiveId(previous.id);
506
+ setRunningId(previous.id);
507
+ setTemplate(
508
+ prettify(
509
+ previous.content
510
+ )
511
+ );
512
+ reload();
513
+ }
514
  }
515
+ }}
516
+ >
517
+ <DeleteForeverIcon />
518
+ </IconButton>
519
+ )}
520
+ </Stack>
521
+ }
522
+ disablePadding
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  >
524
+ <ListItemButton
525
+ dense
526
+ selected={activeId === answer.id}
527
+ // disabled={activeId === answer.id}
528
+ role={undefined}
529
+ onClick={() => {
530
+ setActiveId(answer.id);
531
+ setRunningId(answer.id);
532
+ setTemplate(prettify(answer.content));
533
+ reload();
 
 
 
 
 
 
 
534
  }}
535
+ >
536
+ <ListItemIcon>
537
+ {runningId === answer.id ? (
538
+ <CheckIcon />
539
+ ) : (
540
+ <VisibilityIcon />
541
+ )}
542
+ </ListItemIcon>
543
+
544
+ <ListItemText
545
+ primary={answer.task}
546
+ primaryTypographyProps={{
547
+ sx: {
548
+ overflow: "hidden",
549
+ textOverflow: "ellipsis",
550
+ whiteSpace: "nowrap",
551
+ fontSize: 16,
552
+ },
553
+ }}
554
+ />
555
+ </ListItemButton>
556
+ </ListItem>
557
+ );
558
+ })}
559
+ </List>
560
+ </Paper>
561
  </Stack>
562
  </Stack>
563
+ <Stack
564
+ sx={{
565
+ flex: 1,
566
+ width: { md: "50%" },
567
+ height: { xs: "50%", md: "auto" },
568
+ position: "relative",
569
+ }}
570
+ >
571
  <AppBar position="static" elevation={0} color="default">
572
  <Toolbar>
573
  <IconButton
574
+ edge="start"
575
  color="inherit"
576
  aria-label="Reload"
577
  onClick={() => {
 
580
  >
581
  <ReplayIcon />
582
  </IconButton>
583
+ {current && current.id !== "1" && (
584
+ <>
585
+ <Codepen title={current.task} content={current.content} />
586
+ <Codesandbox title={current.task} content={current.content} />
587
+ </>
588
+ )}
589
+ <Box sx={{ flex: 1 }} />
590
+
591
+ <InfoMenu />
592
+
593
+ <Link href="/" aria-label="home" style={{ color: "inherit" }}>
594
+ <Box
595
+ component="svg"
596
+ viewBox="0 0 24 24"
597
+ sx={{ fontSize: "2em", height: "1em", width: "1em" }}
598
+ >
599
+ <path
600
+ fill="currentColor"
601
+ d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
602
+ />
603
+ </Box>
604
+ </Link>
605
  </Toolbar>
606
  </AppBar>
607
  {loadingLive && (
 
628
  overflow: "hidden",
629
  visibility: loadingLive ? "hidden" : undefined,
630
  }}
631
+ onLoad={() => {
632
+ if (current) {
633
+ setLoadingLive(true);
634
+ setTries(1);
635
+ connection.current = false;
636
+ call({ template: current.content });
637
+ }
638
+ }}
639
  src="/live"
640
  />
641
  </Stack>
642
  </Stack>
643
+
644
+ <SimpleSnackbar
645
+ handleClose={handleSnackbarClose}
646
+ showError={showError}
647
+ message={errorMessage}
648
+ />
649
  </>
650
  );
651
  }
src/pages/live.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import Script from "next/script";
 
2
 
3
  const styles = (
4
  <style>
@@ -12,7 +13,7 @@ const styles = (
12
  height: 100%;
13
  width: 100%;
14
  overflow: hidden;
15
- background: #a9a9a9;
16
  }
17
  #__next {
18
  display: contents;
@@ -24,7 +25,7 @@ export default function Page() {
24
  return (
25
  <>
26
  {styles}
27
- <canvas id="canvas" style={{}} />
28
  <Script src="/js/utils.js" />
29
  </>
30
  );
 
1
  import Script from "next/script";
2
+ import TWEEN from "@tweenjs/tween.js";
3
 
4
  const styles = (
5
  <style>
 
13
  height: 100%;
14
  width: 100%;
15
  overflow: hidden;
16
+ background: #a9a9a9;
17
  }
18
  #__next {
19
  display: contents;
 
25
  return (
26
  <>
27
  {styles}
28
+ <canvas id="canvas" />
29
  <Script src="/js/utils.js" />
30
  </>
31
  );
src/services/api/index.ts CHANGED
@@ -1,14 +1,15 @@
1
  import { ChatCompletionRequestMessage } from "openai";
2
  import { nanoid } from "nanoid";
3
  import { openai } from "@/services/api/openai";
4
- import { extractCode, miniPrompt } from "@/utils";
5
 
6
  export async function toOpenAI({
 
7
  prompt = "extend the code",
8
  negativePrompt = "",
 
9
  template = "",
10
  model = "gpt-3.5-turbo",
11
- temperature = "0.2",
12
  maxTokens = "2048",
13
  }) {
14
  const negativePrompt_ = negativePrompt.trim();
@@ -17,10 +18,9 @@ export async function toOpenAI({
17
  const nextMessage: ChatCompletionRequestMessage = {
18
  role: "user",
19
  content: miniPrompt`
20
- ADD: ${prompt_}
21
- ${negativePrompt_ ? `REMOVE: ${negativePrompt_}` : ""}
22
  TEMPLATE:
23
- \`\`\`js
24
  ${template.trim().replace(/^\s+/gm, "").replace(/^\n+/g, "").replace(/\s+/, " ")}
25
  \`\`\`
26
  `,
@@ -31,35 +31,53 @@ export async function toOpenAI({
31
  try {
32
  const response = await openai.createChatCompletion({
33
  model,
 
34
  temperature: Number.parseFloat(temperature),
35
  messages: [
36
  {
37
  role: "system",
38
  content: miniPrompt`
39
- As a JavaScript expert, you optimize performance and create interactive experiences. You are absurdly creative.
40
- Follow these guidelines closely for optimal results:
41
 
42
- * Use "ADD" and "REMOVE" guidelines to modify code as needed.
43
- * Follow TODO statements
44
- * Always output the complete code, including the original "TEMPLATE" minus "REMOVE" plus "ADD".
45
- * Use valid JavaScript exclusively in a markdown code block using the provided "TEMPLATE".
46
- * Keep a "CHANGELOG" to document changes made to the code.
47
- * Always change the code in some way
48
- * Modify the "TEMPLATE" when adding new code elements for continuous improvement.
49
- `,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  },
51
  nextMessage,
52
  ],
53
- max_tokens: Number.parseInt(maxTokens, 10),
54
  });
55
 
56
  const { message } = response.data.choices[0];
57
 
58
  if (message) {
 
 
59
  return {
60
  ...message,
61
  content: extractCode(message.content).replace(
62
- /(ADD|TEMPLATE|OUTPUT FORMAT|REMOVE).*\n/,
63
  ""
64
  ),
65
  task,
 
1
  import { ChatCompletionRequestMessage } from "openai";
2
  import { nanoid } from "nanoid";
3
  import { openai } from "@/services/api/openai";
4
+ import { extractCode, miniPrompt } from "@/utils/prompt";
5
 
6
  export async function toOpenAI({
7
+ command = "CREATE_GAME",
8
  prompt = "extend the code",
9
  negativePrompt = "",
10
+ temperature = "0.2",
11
  template = "",
12
  model = "gpt-3.5-turbo",
 
13
  maxTokens = "2048",
14
  }) {
15
  const negativePrompt_ = negativePrompt.trim();
 
18
  const nextMessage: ChatCompletionRequestMessage = {
19
  role: "user",
20
  content: miniPrompt`
21
+ "${command}": ${prompt_}. Return the full source code of the game.
 
22
  TEMPLATE:
23
+ \`\`\`javascript
24
  ${template.trim().replace(/^\s+/gm, "").replace(/^\n+/g, "").replace(/\s+/, " ")}
25
  \`\`\`
26
  `,
 
31
  try {
32
  const response = await openai.createChatCompletion({
33
  model,
34
+ max_tokens: Number.parseInt(maxTokens),
35
  temperature: Number.parseFloat(temperature),
36
  messages: [
37
  {
38
  role: "system",
39
  content: miniPrompt`
40
+ You are a skilled 2D game developer working with JavaScript on Canvas2D.
41
+ You understand and follow a set of "COMMANDS" to build and modify games.
42
 
43
+ - "CREATE_GAME": You initiate the development of a game. You consider the game type, environment, basic mechanics and extend the "TEMPLATE".
44
+ - "ADD_FEATURE": You add new features to the game like power-ups, enemies, or levels.
45
+ - "REMOVE_FEATURE": You can remove any existing feature from the game.
46
+ - "UPDATE_FEATURE": You can modify an existing feature in the game, altering its behavior or properties.
47
+ - "FIX_BUG": You debug and fix problems in the game, ensuring everything functions as intended.
48
+
49
+ You NEVER use any (external) assets: image, base64, sprite or audio.
50
+ You can use these globally available libraries without importing them: TWEEN.
51
+ Never use alert! Write your message on Canvas directly.
52
+ You aim for high performance.
53
+ Your "OUTPUT FORMAT" must be valid JavaScript code within a markdown code block.
54
+ It's crucial to follow these "COMMANDS" and "OUTPUT FORMAT" for the desired results.
55
+ `,
56
+ // content: miniPrompt`
57
+ // You are a 2D Game developer and use JavaScript to create full games on Canvas2D.
58
+ // You can choose to add highscore, levels, player life, power ups, enemies.
59
+ // You NEVER add assets like images or audio, everything you use is generated.
60
+ // You use space key for jumping or shooting; arrow left, bottom, right for movement
61
+ // You have a keen eye for performance optimization and are highly skilled in creating interactive experiences.
62
+ // When working on new features, you follow the "ADD" guidelines, and when necessary, remove or exclude elements using "REMOVE".
63
+ // You also pay close attention to "TEMPLATE" code, extending or fixing it as needed.
64
+ // Your "OUTPUT FORMAT" must be exclusively valid JavaScript in a markdown code block, which you achieve by using the provided "TEMPLATE".
65
+ // And remember, the "ADD", "REMOVE", "TEMPLATE", and "OUTPUT FORMAT" guidelines are crucial to follow for optimal results.
66
+ // `,
67
  },
68
  nextMessage,
69
  ],
 
70
  });
71
 
72
  const { message } = response.data.choices[0];
73
 
74
  if (message) {
75
+ console.log("ORIGINAL OUTPUT");
76
+ console.log(message.content);
77
  return {
78
  ...message,
79
  content: extractCode(message.content).replace(
80
+ /(COMMANDS|CREATE_GAME|ADD_FEATURE|REMOVE_FEATURE|UPDATE_FEATURE|FIX_BUG|TEMPLATE|OUTPUT FORMAT).*\n/,
81
  ""
82
  ),
83
  task,
src/store/atoms.ts CHANGED
@@ -8,11 +8,11 @@ export const answersAtom = atomWithStorage<
8
  content: string;
9
  task: string;
10
  }[]
11
- >("fail4", [
12
  {
13
  id: "1",
14
  content: base.default,
15
- task: "Base Script",
16
  },
17
  ]);
18
- export const showCodeAtom = atomWithStorage("fail4-editor", false);
 
8
  content: string;
9
  task: string;
10
  }[]
11
+ >("2DGameGPT", [
12
  {
13
  id: "1",
14
  content: base.default,
15
+ task: "Base Game",
16
  },
17
  ]);
18
+ export const showCodeAtom = atomWithStorage("2DGameGPT-editor", false);
src/utils/index.ts CHANGED
@@ -1,5 +1,6 @@
1
  import GPT3Tokenizer from "gpt3-tokenizer";
2
  import prettier from "prettier";
 
3
 
4
  export const tokenizer = new GPT3Tokenizer({ type: "gpt3" });
5
 
@@ -9,8 +10,13 @@ export function getTokens(text: string) {
9
 
10
  export function prettify(code: string) {
11
  try {
12
- return prettier.format(code, { useTabs: true, semi: true, parser: "babel" });
13
- } catch {
 
 
 
 
 
14
  return code;
15
  }
16
  }
@@ -24,5 +30,3 @@ export function getTheme(mode: string | undefined, systemMode: string | undefine
24
  }
25
  return undefined;
26
  }
27
- export { extractCode } from "@/utils/prompt";
28
- export { miniPrompt } from "@/utils/prompt";
 
1
  import GPT3Tokenizer from "gpt3-tokenizer";
2
  import prettier from "prettier";
3
+ import parserBabel from "prettier/parser-babel";
4
 
5
  export const tokenizer = new GPT3Tokenizer({ type: "gpt3" });
6
 
 
10
 
11
  export function prettify(code: string) {
12
  try {
13
+ return prettier.format(code, {
14
+ useTabs: true,
15
+ semi: true,
16
+ parser: "babel",
17
+ plugins: [parserBabel],
18
+ });
19
+ } catch (error) {
20
  return code;
21
  }
22
  }
 
30
  }
31
  return undefined;
32
  }
 
 
src/utils/share.tsx ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { renderToString } from "react-dom/server";
2
+
3
+ export const wrappers: Record<"js" | "html" | "css" | "miniHtml", (content?: string) => string> = {
4
+ html(content) {
5
+ return `<!DOCTYPE html>
6
+ <!-- generated with https://failfa.st -->
7
+ ${renderToString(
8
+ <html lang="en">
9
+ <head>
10
+ <meta charSet="utf-8" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
12
+ <title>{content}</title>
13
+ <script defer src="/script.js" />
14
+ <link rel="stylesheet" href="/style.css" />
15
+ </head>
16
+ <body>
17
+ <canvas id="canvas" />
18
+ <a className="failfast" href="https://failfa.st" target="_blank">
19
+ AI generated with
20
+ <svg viewBox="0 0 24 24">
21
+ <path
22
+ fill="currentColor"
23
+ d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
24
+ />
25
+ </svg>
26
+ </a>
27
+ </body>
28
+ </html>
29
+ )}`;
30
+ },
31
+ miniHtml() {
32
+ return `
33
+ <!-- generated with https://failfa.st -->
34
+ ${renderToString(
35
+ <>
36
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
37
+
38
+ <canvas id="canvas" />
39
+ <a className="failfast" href="https://failfa.st" target="_blank">
40
+ AI generated with
41
+ <svg viewBox="0 0 24 24">
42
+ <path
43
+ fill="currentColor"
44
+ d="m8,12c-.55,0-1-.45-1-1,0,0,0,0,0,0h-1v-1h4v1h-1s0,0,0,0c0,.55-.45,1-1,1Zm-4-2v4l.97,1.56-.02-4.03-.96-1.53Zm4,8h4v-1h-4v1Zm4,2v2s3,0,3,0l2.5-4h-4.3l-1.2,2Zm8-12v6l-1.28,2.05-4.33-.04.61-1.01h-3s0-13,0-13c7,0,8,6,8,6Zm-2,3l-2-2-2,2.01,2,1.99,2-2Zm-7,4h-1v1h1v-1Z"
45
+ />
46
+ </svg>
47
+ </a>
48
+ </>
49
+ )}`;
50
+ },
51
+ css() {
52
+ return `/**
53
+ * generated with https://failfa.st
54
+ */
55
+
56
+ * {
57
+ margin: 0;
58
+ padding: 0;
59
+ box-sizing: border-box;
60
+ }
61
+ html, body {
62
+ height: 100%;
63
+ width: 100%;
64
+ overflow: hidden;
65
+ background: #a9a9a9;
66
+ }
67
+ .failfast {
68
+ position: fixed;
69
+ display: "flex";
70
+ align-items: center;
71
+ align-content: center;
72
+ z-index: 1;
73
+ top: 0;
74
+ left: 0;
75
+ margin: 8px;
76
+ padding: 6px 16px;
77
+ background: black;
78
+ color: white;
79
+ text-decoration: none;
80
+ border-radius: 4px;
81
+ font-family: sans-serif;
82
+ }
83
+ .failfast svg {
84
+ height: 1em;
85
+ width: 1em;
86
+ font-size: 24px;
87
+ margin: 0 0 -4px 4px;
88
+ }
89
+ `;
90
+ },
91
+ js(content) {
92
+ return `
93
+ /**
94
+ * generated with https://failfa.st
95
+ */
96
+ function __2DGameGPT__ResizeHelper(){
97
+ function handleResize() {
98
+ requestAnimationFrame(() => {
99
+ canvas.width = window.innerWidth;
100
+ canvas.height = window.innerHeight;
101
+ });
102
+ }
103
+ handleResize();
104
+ window.addEventListener("resize", handleResize, { passive: true });
105
+ }
106
+ __2DGameGPT__ResizeHelper()
107
+ ${content}
108
+ `;
109
+ },
110
+ };