NERDDISCO commited on
Commit
2f65818
•
1 Parent(s): 32a1ca4

feat: everything

Browse files
.github/workflows/sync_to_hf_spaces.yml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Spaces
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ workflow_dispatch:
6
+
7
+ env:
8
+ HF_USERNAME: failfast
9
+ HF_SPACE_NAME: 2D-GameCreator-GPT
10
+
11
+ jobs:
12
+ sync-to-space:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v3
16
+ with:
17
+ fetch-depth: 0
18
+ lfs: true
19
+ - name: Push to Space
20
+ env:
21
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
22
+ run: git push --force https://$HF_USERNAME:[email protected]/spaces/$HF_USERNAME/$HF_SPACE_NAME main
README.md CHANGED
@@ -1,7 +1,15 @@
 
 
 
 
 
 
 
 
 
 
1
  <h1 align="center"><big>2D GameCreator-GPT</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
  ---
@@ -46,7 +54,7 @@ hosted demo on 🤗 Spaces or run it directly on your computer.
46
 
47
  ### On 🤗 Spaces
48
 
49
- The **2D GameCreator-GPT** is hosted on Hugging Face Spaces.
50
 
51
  ### On your computer
52
 
@@ -58,3 +66,4 @@ If you want to run the UI locally, please follow these steps!
58
  3. Install the required dependencies using `npm install`.
59
  4. Run the development server with `npm run dev` to access the WebUI.
60
  5. Begin creating your own 2D games in JavaScript
 
 
1
+ ---
2
+ title: 2D GameCreator-GPT
3
+ emoji: "\U0001F47E\U0001F579\U0001F4FA"
4
+ colorFrom: purple
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: false
8
+ license: agpl-3.0
9
+ app_port: 3000
10
+ ---
11
  <h1 align="center"><big>2D GameCreator-GPT</big></h1>
12
 
 
 
13
  [![Discord](https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge)](https://discord.com/invite/m3TBB9XEkb)
14
 
15
  ---
 
54
 
55
  ### On 🤗 Spaces
56
 
57
+ [The **2D GameCreator-GPT** is hosted on Hugging Face Spaces](https://huggingface.co/spaces/failfast/2D-GameCreator-GPT/)
58
 
59
  ### On your computer
60
 
 
66
  3. Install the required dependencies using `npm install`.
67
  4. Run the development server with `npm run dev` to access the WebUI.
68
  5. Begin creating your own 2D games in JavaScript
69
+
next.config.js CHANGED
@@ -1,6 +1,7 @@
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
- reactStrictMode: true,
4
- }
 
5
 
6
- module.exports = nextConfig
 
1
  /** @type {import('next').NextConfig} */
2
  const nextConfig = {
3
+ output: "standalone",
4
+ reactStrictMode: true,
5
+ };
6
 
7
+ module.exports = nextConfig;
package-lock.json CHANGED
@@ -20,6 +20,7 @@
20
  "@tweenjs/tween.js": "^18.6.4",
21
  "@types/prettier": "^2.7.2",
22
  "axios": "1.3.5",
 
23
  "codesandbox": "2.2.3",
24
  "esdeka": "0.1.18",
25
  "eslint": "8.37.0",
@@ -28,10 +29,11 @@
28
  "jotai": "2.0.4",
29
  "monaco-editor": "0.37.1",
30
  "monaco-themes": "0.4.4",
31
- "mousetrap": "^1.6.5",
32
  "nanoid": "4.0.2",
33
  "next": "13.2.4",
34
  "openai": "^3.2.1",
 
35
  "prettier": "2.8.7",
36
  "react": "18.2.0",
37
  "react-dom": "18.2.0",
@@ -41,6 +43,7 @@
41
  },
42
  "devDependencies": {
43
  "@semantic-release/git": "^10.0.1",
 
44
  "@types/mousetrap": "^1.6.11",
45
  "@types/node": "18.15.11",
46
  "@types/react-syntax-highlighter": "^15.5.6",
@@ -1040,6 +1043,12 @@
1040
  "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
1041
  "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ=="
1042
  },
 
 
 
 
 
 
1043
  "node_modules/@types/hast": {
1044
  "version": "2.3.4",
1045
  "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
@@ -1827,6 +1836,15 @@
1827
  }
1828
  ]
1829
  },
 
 
 
 
 
 
 
 
 
1830
  "node_modules/capture-stack-trace": {
1831
  "version": "1.0.2",
1832
  "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz",
@@ -3140,6 +3158,11 @@
3140
  "node": ">=0.10.0"
3141
  }
3142
  },
 
 
 
 
 
3143
  "node_modules/execa": {
3144
  "version": "5.1.1",
3145
  "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -6098,6 +6121,15 @@
6098
  "node": ">=0.10.0"
6099
  }
6100
  },
 
 
 
 
 
 
 
 
 
6101
  "node_modules/path-exists": {
6102
  "version": "4.0.0",
6103
  "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6140,6 +6172,15 @@
6140
  "node": ">=8"
6141
  }
6142
  },
 
 
 
 
 
 
 
 
 
6143
  "node_modules/picocolors": {
6144
  "version": "1.0.0",
6145
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -6242,6 +6283,14 @@
6242
  "node": ">=6"
6243
  }
6244
  },
 
 
 
 
 
 
 
 
6245
  "node_modules/process-nextick-args": {
6246
  "version": "2.0.1",
6247
  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@@ -7588,11 +7637,24 @@
7588
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
7589
  }
7590
  },
 
 
 
 
 
 
 
 
7591
  "node_modules/util-deprecate": {
7592
  "version": "1.0.2",
7593
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
7594
  "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
7595
  },
 
 
 
 
 
7596
  "node_modules/validate-npm-package-license": {
7597
  "version": "3.0.4",
7598
  "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
 
20
  "@tweenjs/tween.js": "^18.6.4",
21
  "@types/prettier": "^2.7.2",
22
  "axios": "1.3.5",
23
+ "canvas-confetti": "1.4.0",
24
  "codesandbox": "2.2.3",
25
  "esdeka": "0.1.18",
26
  "eslint": "8.37.0",
 
29
  "jotai": "2.0.4",
30
  "monaco-editor": "0.37.1",
31
  "monaco-themes": "0.4.4",
32
+ "mousetrap": "1.6.5",
33
  "nanoid": "4.0.2",
34
  "next": "13.2.4",
35
  "openai": "^3.2.1",
36
+ "phaser": "^3.55.2",
37
  "prettier": "2.8.7",
38
  "react": "18.2.0",
39
  "react-dom": "18.2.0",
 
43
  },
44
  "devDependencies": {
45
  "@semantic-release/git": "^10.0.1",
46
+ "@types/canvas-confetti": "^1.6.0",
47
  "@types/mousetrap": "^1.6.11",
48
  "@types/node": "18.15.11",
49
  "@types/react-syntax-highlighter": "^15.5.6",
 
1043
  "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
1044
  "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ=="
1045
  },
1046
+ "node_modules/@types/canvas-confetti": {
1047
+ "version": "1.6.0",
1048
+ "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.0.tgz",
1049
+ "integrity": "sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==",
1050
+ "dev": true
1051
+ },
1052
  "node_modules/@types/hast": {
1053
  "version": "2.3.4",
1054
  "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
 
1836
  }
1837
  ]
1838
  },
1839
+ "node_modules/canvas-confetti": {
1840
+ "version": "1.4.0",
1841
+ "resolved": "https://registry.npmjs.org/canvas-confetti/-/canvas-confetti-1.4.0.tgz",
1842
+ "integrity": "sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ==",
1843
+ "funding": {
1844
+ "type": "donate",
1845
+ "url": "https://www.paypal.me/kirilvatev"
1846
+ }
1847
+ },
1848
  "node_modules/capture-stack-trace": {
1849
  "version": "1.0.2",
1850
  "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.2.tgz",
 
3158
  "node": ">=0.10.0"
3159
  }
3160
  },
3161
+ "node_modules/eventemitter3": {
3162
+ "version": "4.0.7",
3163
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
3164
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
3165
+ },
3166
  "node_modules/execa": {
3167
  "version": "5.1.1",
3168
  "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
 
6121
  "node": ">=0.10.0"
6122
  }
6123
  },
6124
+ "node_modules/path": {
6125
+ "version": "0.12.7",
6126
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
6127
+ "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
6128
+ "dependencies": {
6129
+ "process": "^0.11.1",
6130
+ "util": "^0.10.3"
6131
+ }
6132
+ },
6133
  "node_modules/path-exists": {
6134
  "version": "4.0.0",
6135
  "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
 
6172
  "node": ">=8"
6173
  }
6174
  },
6175
+ "node_modules/phaser": {
6176
+ "version": "3.55.2",
6177
+ "resolved": "https://registry.npmjs.org/phaser/-/phaser-3.55.2.tgz",
6178
+ "integrity": "sha512-amKXsbb2Ht29dGPKvt1edq3yGGYKtq8373GpJYGKPNPnneYY6MtVTOgjHDuZwtmUyK4v86FugkT3hzW/N4tjxQ==",
6179
+ "dependencies": {
6180
+ "eventemitter3": "^4.0.7",
6181
+ "path": "^0.12.7"
6182
+ }
6183
+ },
6184
  "node_modules/picocolors": {
6185
  "version": "1.0.0",
6186
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
 
6283
  "node": ">=6"
6284
  }
6285
  },
6286
+ "node_modules/process": {
6287
+ "version": "0.11.10",
6288
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
6289
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
6290
+ "engines": {
6291
+ "node": ">= 0.6.0"
6292
+ }
6293
+ },
6294
  "node_modules/process-nextick-args": {
6295
  "version": "2.0.1",
6296
  "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
 
7637
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
7638
  }
7639
  },
7640
+ "node_modules/util": {
7641
+ "version": "0.10.4",
7642
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
7643
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
7644
+ "dependencies": {
7645
+ "inherits": "2.0.3"
7646
+ }
7647
+ },
7648
  "node_modules/util-deprecate": {
7649
  "version": "1.0.2",
7650
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
7651
  "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
7652
  },
7653
+ "node_modules/util/node_modules/inherits": {
7654
+ "version": "2.0.3",
7655
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
7656
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
7657
+ },
7658
  "node_modules/validate-npm-package-license": {
7659
  "version": "3.0.4",
7660
  "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
package.json CHANGED
@@ -55,6 +55,7 @@
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",
@@ -63,10 +64,11 @@
63
  "jotai": "2.0.4",
64
  "monaco-editor": "0.37.1",
65
  "monaco-themes": "0.4.4",
66
- "mousetrap": "^1.6.5",
67
  "nanoid": "4.0.2",
68
  "next": "13.2.4",
69
  "openai": "^3.2.1",
 
70
  "prettier": "2.8.7",
71
  "react": "18.2.0",
72
  "react-dom": "18.2.0",
@@ -76,6 +78,7 @@
76
  },
77
  "devDependencies": {
78
  "@semantic-release/git": "^10.0.1",
 
79
  "@types/mousetrap": "^1.6.11",
80
  "@types/node": "18.15.11",
81
  "@types/react-syntax-highlighter": "^15.5.6",
 
55
  "@tweenjs/tween.js": "^18.6.4",
56
  "@types/prettier": "^2.7.2",
57
  "axios": "1.3.5",
58
+ "canvas-confetti": "1.4.0",
59
  "codesandbox": "2.2.3",
60
  "esdeka": "0.1.18",
61
  "eslint": "8.37.0",
 
64
  "jotai": "2.0.4",
65
  "monaco-editor": "0.37.1",
66
  "monaco-themes": "0.4.4",
67
+ "mousetrap": "1.6.5",
68
  "nanoid": "4.0.2",
69
  "next": "13.2.4",
70
  "openai": "^3.2.1",
71
+ "phaser": "^3.55.2",
72
  "prettier": "2.8.7",
73
  "react": "18.2.0",
74
  "react-dom": "18.2.0",
 
78
  },
79
  "devDependencies": {
80
  "@semantic-release/git": "^10.0.1",
81
+ "@types/canvas-confetti": "^1.6.0",
82
  "@types/mousetrap": "^1.6.11",
83
  "@types/node": "18.15.11",
84
  "@types/react-syntax-highlighter": "^15.5.6",
public/failfast.svg ADDED
public/img/space_invaders_extreme.jpg ADDED
src/components/Codepen.tsx CHANGED
@@ -3,8 +3,13 @@ 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
 
3
  import Tooltip from "@mui/material/Tooltip";
4
  import { CodepenIcon } from "@/components/CodepenIcon";
5
  import { ShareProps } from "../pages";
6
+ import { prettify } from "@/utils";
7
 
8
  export function Codepen({ title, content }: ShareProps) {
9
+ // location.reload is not allowed on CodePen
10
+ content = content.replace("location.reload()", "history.go(0)");
11
+ content = prettify(content);
12
+
13
  return (
14
  <form action="https://codepen.io/pen/define" method="POST" target="_blank">
15
  <input
src/components/Codesandbox.tsx CHANGED
@@ -2,11 +2,11 @@ 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"
 
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 "@/components/GameCreator";
6
 
7
  export function Codesandbox({ title, content }: ShareProps) {
8
  return (
9
+ <Tooltip title="Save to Codesandbox">
10
  <IconButton
11
  color="primary"
12
  aria-label="Codsandbox"
src/components/Examples.tsx ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Card,
3
+ CardContent,
4
+ CardHeader,
5
+ CardMedia,
6
+ Grid,
7
+ Link,
8
+ List,
9
+ ListItem,
10
+ ListItemIcon,
11
+ ListItemText,
12
+ Table,
13
+ TableBody,
14
+ TableCell,
15
+ TableRow,
16
+ Typography,
17
+ } from "@mui/material";
18
+ import { DividerBox, SectionBox } from "./base/boxes";
19
+ import { DynamicFeed, PrecisionManufacturing } from "@mui/icons-material";
20
+
21
+ export default function Examples() {
22
+ return (
23
+ <>
24
+ <DividerBox />
25
+
26
+ <SectionBox>
27
+ <Typography component="h3" variant="h2">
28
+ Examples
29
+ </Typography>
30
+ </SectionBox>
31
+
32
+ <Grid container>
33
+ <Grid item sm={4}>
34
+ <Card>
35
+ <CardHeader title="Space Invaders Extreme"></CardHeader>
36
+ <CardMedia
37
+ component="img"
38
+ image="img/space_invaders_extreme.jpg"
39
+ height="256"
40
+ />
41
+ <CardContent>
42
+ <Table>
43
+ <TableBody>
44
+ <TableRow>
45
+ <TableCell variant="head">Play</TableCell>
46
+ <TableCell>
47
+ {" "}
48
+ <Link
49
+ href="https://codesandbox.io/s/space-invaders-extreme-7xhp4v"
50
+ target="_blank"
51
+ rel="noopener"
52
+ >
53
+ via Codesandbox
54
+ </Link>
55
+ </TableCell>
56
+ </TableRow>
57
+
58
+ <TableRow>
59
+ <TableCell variant="head">Model</TableCell>
60
+ <TableCell>GPT 3.5 Turbo</TableCell>
61
+ </TableRow>
62
+ <TableRow>
63
+ <TableCell variant="head">Iterations</TableCell>
64
+ <TableCell>20</TableCell>
65
+ </TableRow>
66
+ <TableRow>
67
+ <TableCell variant="head">Controls</TableCell>
68
+ <TableCell>
69
+ Keyboard: Movement (Arrow Left, Arrow right), Shooting
70
+ (Space), Restart (R)
71
+ </TableCell>
72
+ </TableRow>
73
+ <TableRow>
74
+ <TableCell variant="head">Hints</TableCell>
75
+ <TableCell>
76
+ The bigger the window, the easier it gets to play
77
+ </TableCell>
78
+ </TableRow>
79
+ </TableBody>
80
+ </Table>
81
+ </CardContent>
82
+ </Card>
83
+ </Grid>
84
+ </Grid>
85
+
86
+ <DividerBox />
87
+ </>
88
+ );
89
+ }
src/components/GameCreator.tsx ADDED
@@ -0,0 +1,691 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { SyntheticEvent, useEffect, useMemo, useRef, useState } from "react";
2
+
3
+ import axios, { AxiosError, AxiosError } from "axios";
4
+ import KeyIcon from "@mui/icons-material/Key";
5
+ import SmartButtonIcon from "@mui/icons-material/SmartButton";
6
+ import AcUnitIcon from "@mui/icons-material/AcUnit";
7
+ import LocalFireDepartmentIcon from "@mui/icons-material/LocalFireDepartment";
8
+ import CheckIcon from "@mui/icons-material/Check";
9
+ import ClearIcon from "@mui/icons-material/Clear";
10
+ import CodeIcon from "@mui/icons-material/Code";
11
+ import CodeOffIcon from "@mui/icons-material/CodeOff";
12
+ import EditIcon from "@mui/icons-material/Edit";
13
+ import VisibilityIcon from "@mui/icons-material/Visibility";
14
+ import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
15
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
16
+ import PlayArrowIcon from "@mui/icons-material/PlayArrow";
17
+ import ReplayIcon from "@mui/icons-material/Replay";
18
+ import MoneyIcon from "@mui/icons-material/Money";
19
+ import TollIcon from "@mui/icons-material/Toll";
20
+ import TextField from "@mui/material/TextField";
21
+ import Box from "@mui/material/Box";
22
+ import Stack from "@mui/material/Stack";
23
+ import Accordion from "@mui/material/Accordion";
24
+ import Typography from "@mui/material/Typography";
25
+ import AccordionSummary from "@mui/material/AccordionSummary";
26
+ import AccordionDetails from "@mui/material/AccordionDetails";
27
+ import Paper from "@mui/material/Paper";
28
+ import IconButton from "@mui/material/IconButton";
29
+ import List from "@mui/material/List";
30
+ import ListItem from "@mui/material/ListItem";
31
+ import { nanoid } from "nanoid";
32
+ import AppBar from "@mui/material/AppBar";
33
+ import Toolbar from "@mui/material/Toolbar";
34
+ import ListItemIcon from "@mui/material/ListItemIcon";
35
+ import ListItemButton from "@mui/material/ListItemButton";
36
+ import ListItemText from "@mui/material/ListItemText";
37
+ import { useHost } from "esdeka/react";
38
+ import CircularProgress from "@mui/material/CircularProgress";
39
+ import CssBaseline from "@mui/material/CssBaseline";
40
+ import Slider from "@mui/material/Slider";
41
+ import { useAtom } from "jotai";
42
+ import Button from "@mui/material/Button";
43
+ import dynamic from "next/dynamic";
44
+ import FormControl from "@mui/material/FormControl";
45
+ import InputLabel from "@mui/material/InputLabel";
46
+ import Select from "@mui/material/Select";
47
+ import MenuItem from "@mui/material/MenuItem";
48
+ import { useColorScheme } from "@mui/material/styles";
49
+ import { getTheme, prettify } from "@/utils";
50
+ import { answersAtom, showCodeAtom } from "@/store/atoms";
51
+ import {
52
+ COMMAND_ADD_FEATURE,
53
+ COMMAND_CREATE_GAME,
54
+ COMMAND_EXTEND_FEATURE,
55
+ COMMAND_FIX_BUG,
56
+ COMMAND_LABEL_ADD_FEATURE,
57
+ COMMAND_LABEL_CREATE_GAME,
58
+ COMMAND_LABEL_EXTEND_FEATURE,
59
+ COMMAND_LABEL_FIX_BUG,
60
+ COMMAND_LABEL_REMOVE_FEATURE,
61
+ COMMAND_REMOVE_FEATURE,
62
+ } from "@/constants";
63
+ import { baseGame } from "@/constants/baseGame";
64
+ import { EditTitle } from "@/components/EditTitle";
65
+ import theme, { fontMono } from "@/lib/theme";
66
+ import { Codesandbox } from "@/components/Codesandbox";
67
+ import { Codepen } from "@/components/Codepen";
68
+ import { InfoMenu } from "@/components/InfoMenu";
69
+ import SimpleSnackbar from "@/components/SimpleSnackbar";
70
+ import ExampleButton from "@/components/base/ExampleButton";
71
+ import { Container, Grid, Link, ListSubheader } from "@mui/material";
72
+ import Secret from "@/components/base/secret";
73
+ import Footer from "@/components/footer";
74
+ import Title from "@/components/title";
75
+ import { RunCircle } from "@mui/icons-material";
76
+ import Introduction from "@/components/Introduction";
77
+ import Instructions from "@/components/Instructions";
78
+ import Examples from "@/components/Examples";
79
+ const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
80
+
81
+ export interface ShareProps {
82
+ title: string;
83
+ content: string;
84
+ }
85
+
86
+ export default function GameCreator() {
87
+ const ref = useRef<HTMLIFrameElement>(null);
88
+ const [prompt, setPrompt] = useState("");
89
+ const [template, setTemplate] = useState(prettify(baseGame.default));
90
+ const [runningId, setRunningId] = useState("1");
91
+ const [activeId, setActiveId] = useState("1");
92
+ const [editingId, setEditingId] = useState<string | null>(null);
93
+ const [answers, setAnswers] = useAtom(answersAtom);
94
+ const [showCode, setShowCode] = useAtom(showCodeAtom);
95
+ const [loading, setLoading] = useState(false);
96
+ const [loadingLive, setLoadingLive] = useState(true);
97
+ const [showError, setShowError] = useState(false);
98
+ const [errorMessage, setErrorMessage] = useState("");
99
+
100
+ const { mode, systemMode } = useColorScheme();
101
+
102
+ const { call, subscribe } = useHost(ref, "2DGameGPT");
103
+
104
+ const connection = useRef(false);
105
+ const [tries, setTries] = useState(1);
106
+
107
+ // Send a connection request
108
+ useEffect(() => {
109
+ const current = answers.find(({ id }) => id === runningId);
110
+ if (connection.current || tries <= 0) {
111
+ return () => {
112
+ /* Consistency */
113
+ };
114
+ }
115
+
116
+ const timeout = setTimeout(() => {
117
+ if (current) {
118
+ call({ template: current.content });
119
+ }
120
+
121
+ setTries(tries - 1);
122
+ }, 1_000);
123
+
124
+ return () => {
125
+ clearTimeout(timeout);
126
+ };
127
+ }, [call, tries, answers, runningId]);
128
+
129
+ useEffect(() => {
130
+ if (!connection.current && loadingLive) {
131
+ const unsubscribe = subscribe(event => {
132
+ const { action } = event.data;
133
+ switch (action.type) {
134
+ case "answer":
135
+ connection.current = true;
136
+ setLoadingLive(false);
137
+ break;
138
+ default:
139
+ break;
140
+ }
141
+ });
142
+ return () => {
143
+ unsubscribe();
144
+ };
145
+ }
146
+ return () => {
147
+ /* Consistency */
148
+ };
149
+ }, [subscribe, loadingLive]);
150
+
151
+ const sortedAnswers = useMemo(() => {
152
+ return [...answers].sort((a, b) => {
153
+ if (a.id === "1") return -1;
154
+ if (b.id === "1") return 1;
155
+ return 0;
156
+ });
157
+ }, [answers]);
158
+
159
+ const current = answers.find(({ id }) => id === activeId);
160
+
161
+ const handleSnackbarClose = (event: SyntheticEvent | Event, reason?: string) => {
162
+ if (reason === "clickaway") {
163
+ return;
164
+ }
165
+
166
+ setShowError(false);
167
+ };
168
+
169
+ function reload() {
170
+ connection.current = false;
171
+ if (ref.current) {
172
+ ref.current.src = `/live?${nanoid()}`;
173
+ setLoadingLive(true);
174
+ setTries(1);
175
+ }
176
+ }
177
+
178
+ return (
179
+ <>
180
+ <Paper sx={{ p: 1 }}>
181
+ <Stack
182
+ sx={{
183
+ ...fontMono.style,
184
+ inset: 0,
185
+ overflow: "hidden",
186
+ flexDirection: { md: "row" },
187
+ height: "100vh",
188
+ }}
189
+ >
190
+ <Stack
191
+ sx={{
192
+ width: { md: "50%" },
193
+ height: { xs: "50%", md: "100%" },
194
+ flex: 1,
195
+ overflow: "hidden",
196
+ }}
197
+ >
198
+ <AppBar position="static" elevation={0} color="default">
199
+ <Toolbar>
200
+ <Typography variant="h5" component="h2" sx={{ flex: 1, m: 0 }}>
201
+ 2D GameCreator-GPT
202
+ </Typography>
203
+
204
+ <IconButton
205
+ color="inherit"
206
+ aria-label={showCode ? "Hide Code" : "Show Code"}
207
+ onClick={() => {
208
+ setShowCode(previousState => !previousState);
209
+ }}
210
+ >
211
+ {showCode ? <CodeOffIcon /> : <CodeIcon />}
212
+ </IconButton>
213
+ {/* <IconButton
214
+ edge="end"
215
+ color="inherit"
216
+ aria-label="Clear Prompt"
217
+ onClick={async () => {
218
+ setActiveId("1");
219
+ setRunningId("1");
220
+ setTemplate(prettify(baseGame.default));
221
+ reload();
222
+ }}
223
+ >
224
+ <ClearIcon />
225
+ </IconButton> */}
226
+ </Toolbar>
227
+ </AppBar>
228
+ {showCode && (
229
+ <Box
230
+ sx={{ flex: 1 }}
231
+ onKeyDown={event => {
232
+ if (event.key === "s" && event.metaKey) {
233
+ event.preventDefault();
234
+ setAnswers(previousAnswers =>
235
+ previousAnswers.map(previousAnswer => {
236
+ return previousAnswer.id === activeId
237
+ ? {
238
+ ...previousAnswer,
239
+ content: template,
240
+ }
241
+ : previousAnswer;
242
+ })
243
+ );
244
+ setTemplate(previousState => prettify(previousState));
245
+ reload();
246
+ }
247
+ }}
248
+ >
249
+ <MonacoEditor
250
+ theme={getTheme(mode, systemMode)}
251
+ language="javascript"
252
+ value={template}
253
+ options={{
254
+ fontSize: 14,
255
+ }}
256
+ onChange={async value => {
257
+ setTemplate(value ?? "");
258
+ }}
259
+ />
260
+ </Box>
261
+ )}
262
+ <Stack
263
+ sx={{
264
+ flex: 1,
265
+ display: showCode ? "none" : undefined,
266
+ overflow: "hidden",
267
+ }}
268
+ >
269
+ <Box
270
+ component="form"
271
+ id="gpt-form"
272
+ sx={{ p: 0, pt: 0 }}
273
+ onSubmit={async event => {
274
+ event.preventDefault();
275
+ const formData = new FormData(event.target as HTMLFormElement);
276
+ const formObject = Object.fromEntries(formData);
277
+ try {
278
+ setLoading(true);
279
+ const { data } = await axios.post(
280
+ "/api/generate",
281
+ formObject
282
+ );
283
+ const answer = data;
284
+ setAnswers(previousAnswers => [answer, ...previousAnswers]);
285
+ setRunningId(answer.id);
286
+ setActiveId(answer.id);
287
+ setTemplate(prettify(answer.content));
288
+ reload();
289
+ } catch (error) {
290
+ setShowError(true);
291
+ setErrorMessage((error as AxiosError).message);
292
+ console.error(error);
293
+ } finally {
294
+ setLoading(false);
295
+ }
296
+ }}
297
+ >
298
+ <Stack sx={{ p: 1, pl: 0, gap: 2 }}>
299
+ <Secret
300
+ label="OpenAI API Key"
301
+ name="openAIAPIKey"
302
+ required={process.env.NODE_ENV === "production"}
303
+ />
304
+
305
+ <Stack direction="row" spacing={1}>
306
+ <TextField
307
+ multiline
308
+ fullWidth
309
+ required
310
+ id="prompt"
311
+ name="prompt"
312
+ label="Prompt"
313
+ value={prompt}
314
+ onChange={e => setPrompt(e.target.value)}
315
+ minRows={5}
316
+ InputProps={{
317
+ style: fontMono.style,
318
+ }}
319
+ />
320
+
321
+ <Stack spacing={1}>
322
+ <FormControl variant="outlined" sx={{ minWidth: 180 }}>
323
+ <InputLabel id="gpt-command-select-label">
324
+ Command
325
+ </InputLabel>
326
+ <Select
327
+ labelId="gpt-command-select-label"
328
+ id="gpt-command-select"
329
+ name="command"
330
+ defaultValue={COMMAND_CREATE_GAME}
331
+ label="Command"
332
+ >
333
+ <MenuItem value={COMMAND_CREATE_GAME}>
334
+ {COMMAND_LABEL_CREATE_GAME}
335
+ </MenuItem>
336
+ <MenuItem value={COMMAND_ADD_FEATURE}>
337
+ {COMMAND_LABEL_ADD_FEATURE}
338
+ </MenuItem>
339
+ <MenuItem value={COMMAND_REMOVE_FEATURE}>
340
+ {COMMAND_LABEL_REMOVE_FEATURE}
341
+ </MenuItem>
342
+ <MenuItem value={COMMAND_EXTEND_FEATURE}>
343
+ {COMMAND_LABEL_EXTEND_FEATURE}
344
+ </MenuItem>
345
+ <MenuItem value={COMMAND_FIX_BUG}>
346
+ {COMMAND_LABEL_FIX_BUG}
347
+ </MenuItem>
348
+ </Select>
349
+ </FormControl>
350
+
351
+ <Button
352
+ form="gpt-form"
353
+ type="submit"
354
+ variant="contained"
355
+ fullWidth
356
+ aria-label={loading ? "Loading" : "Run"}
357
+ aria-disabled={loading}
358
+ disabled={loading}
359
+ startIcon={
360
+ loading ? (
361
+ <CircularProgress size={20} />
362
+ ) : (
363
+ <PlayArrowIcon />
364
+ )
365
+ }
366
+ sx={{
367
+ pl: 5,
368
+ pr: 5,
369
+ flexGrow: 1,
370
+ overflow: "auto",
371
+ }}
372
+ >
373
+ <Typography sx={{ fontWeight: "500" }}>
374
+ Run
375
+ </Typography>
376
+ </Button>
377
+ </Stack>
378
+ </Stack>
379
+
380
+ <Stack direction="row" spacing={1} alignItems="center">
381
+ <Typography>Examples</Typography>
382
+
383
+ <ExampleButton
384
+ title={"Space Invaders"}
385
+ text={
386
+ "Space Invaders. Single player ship at the bottom, a row of invaders at the top moving side-to-side and descending. The player ship can move left and right, and shoot bullets to destroy the invaders. Handle game over scenario when an invader reaches the player's level or all invaders are dead. Collision detection for both invaders and player. "
387
+ }
388
+ onClick={setPrompt}
389
+ />
390
+
391
+ <ExampleButton
392
+ title="Jump & Run"
393
+ text={
394
+ "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."
395
+ }
396
+ onClick={setPrompt}
397
+ />
398
+
399
+ <ExampleButton
400
+ title="Flappy Bird"
401
+ text={
402
+ "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. The pipes have a huge opening so that the bird can easily fly through. 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."
403
+ }
404
+ onClick={setPrompt}
405
+ />
406
+
407
+ <ExampleButton
408
+ title="Asteroids"
409
+ text={
410
+ "Asteroids. Control spaceship-movement using arrow keys. Fire bullets with space key to destroy asteroids, breaking them into smaller pieces. Earn points for destroying asteroids, with higher scores for smaller ones. Collision detection when spaceship hits asteroid, collision reduces spaceship health, game over when health is 0."
411
+ }
412
+ // text={
413
+ // "Asteroids. Space ship can fly around in space using the arrow keys. Irregular shaped objects called asteroids flying around the space ship. The space ship can shoot bullets using the space key. When a bullet hits an asteroid, it splits in smaller irregular shaped objects; when asteroid is completely destroyed, the player scores one point. When the space ship collides with an asteroid, it looses 1 health; when the health is 0, the game is over. Restart the game via pressing space key."
414
+ // }
415
+ onClick={setPrompt}
416
+ />
417
+ </Stack>
418
+
419
+ <Paper variant="outlined">
420
+ <Accordion disableGutters square elevation={0}>
421
+ <AccordionSummary
422
+ expandIcon={<ExpandMoreIcon />}
423
+ aria-controls="gtp-options-content"
424
+ id="gtp-options-header"
425
+ sx={{
426
+ bgcolor: "background.paper",
427
+ color: "text.primary",
428
+ }}
429
+ >
430
+ <Typography>Options</Typography>
431
+ </AccordionSummary>
432
+ <AccordionDetails>
433
+ <Stack gap={2}>
434
+ <FormControl
435
+ fullWidth
436
+ variant="outlined"
437
+ sx={{ mb: 3 }}
438
+ >
439
+ <InputLabel id="gpt-model-select-label">
440
+ Model
441
+ </InputLabel>
442
+ <Select
443
+ labelId="gpt-model-select-label"
444
+ id="gpt-model-select"
445
+ name="model"
446
+ defaultValue="gpt-3.5-turbo"
447
+ label="Model"
448
+ >
449
+ <MenuItem value="gpt-3.5-turbo">
450
+ GPT 3.5 turbo
451
+ </MenuItem>
452
+ <MenuItem value="gpt-4">GPT 4</MenuItem>
453
+ </Select>
454
+ </FormControl>
455
+ </Stack>
456
+ <Stack
457
+ spacing={2}
458
+ direction="row"
459
+ sx={{ mb: 2 }}
460
+ alignItems="center"
461
+ >
462
+ <AcUnitIcon />
463
+ <Slider
464
+ marks
465
+ id="temperature"
466
+ name="temperature"
467
+ min={0}
468
+ max={0.8}
469
+ defaultValue={0.2}
470
+ step={0.1}
471
+ valueLabelDisplay="auto"
472
+ aria-label="Temperature"
473
+ />
474
+ <LocalFireDepartmentIcon />
475
+ </Stack>
476
+ <Stack
477
+ spacing={2}
478
+ direction="row"
479
+ sx={{ mb: 2 }}
480
+ alignItems="center"
481
+ >
482
+ <TollIcon />
483
+ <Slider
484
+ marks
485
+ id="maxTokens"
486
+ name="maxTokens"
487
+ min={1024}
488
+ max={4096}
489
+ defaultValue={2048}
490
+ step={256}
491
+ valueLabelDisplay="auto"
492
+ aria-label="Max Tokens"
493
+ />
494
+ <MoneyIcon />
495
+ </Stack>
496
+ <input
497
+ id="template"
498
+ name="template"
499
+ type="hidden"
500
+ value={template}
501
+ onChange={event => {
502
+ setTemplate(event.target.value);
503
+ }}
504
+ />
505
+ </AccordionDetails>
506
+ </Accordion>
507
+ </Paper>
508
+ </Stack>
509
+ </Box>
510
+
511
+ <Paper variant="elevation" sx={{ p: 1, pl: 0, overflow: "auto" }}>
512
+ <List sx={{ flex: 1, p: 0 }}>
513
+ <ListSubheader
514
+ sx={{
515
+ fontSize: "1em",
516
+ fontWeight: "normal",
517
+ color: "white",
518
+ }}
519
+ >
520
+ Games
521
+ </ListSubheader>
522
+ {sortedAnswers.map((answer, index) => {
523
+ return (
524
+ <ListItem
525
+ key={answer.id}
526
+ secondaryAction={
527
+ <Stack
528
+ sx={{
529
+ flexDirection: "row",
530
+ gap: 1,
531
+ }}
532
+ >
533
+ {answer.id === "1" ? undefined : (
534
+ <IconButton
535
+ edge="end"
536
+ aria-label="Delete"
537
+ onClick={() => {
538
+ setAnswers(previousAnswers =>
539
+ previousAnswers.filter(
540
+ ({ id }) =>
541
+ id !== answer.id
542
+ )
543
+ );
544
+ if (runningId === answer.id) {
545
+ const previous =
546
+ answers[index + 1];
547
+ if (previous) {
548
+ setActiveId(
549
+ previous.id
550
+ );
551
+ setRunningId(
552
+ previous.id
553
+ );
554
+ setTemplate(
555
+ prettify(
556
+ previous.content
557
+ )
558
+ );
559
+ reload();
560
+ }
561
+ }
562
+ }}
563
+ >
564
+ <DeleteForeverIcon />
565
+ </IconButton>
566
+ )}
567
+ </Stack>
568
+ }
569
+ disablePadding
570
+ >
571
+ <ListItemButton
572
+ dense
573
+ selected={activeId === answer.id}
574
+ // disabled={activeId === answer.id}
575
+ role={undefined}
576
+ onClick={() => {
577
+ setActiveId(answer.id);
578
+ setRunningId(answer.id);
579
+ setTemplate(prettify(answer.content));
580
+ reload();
581
+ }}
582
+ >
583
+ <ListItemIcon>
584
+ {runningId === answer.id ? (
585
+ <CheckIcon />
586
+ ) : (
587
+ <VisibilityIcon />
588
+ )}
589
+ </ListItemIcon>
590
+
591
+ <ListItemText
592
+ primary={answer.task}
593
+ primaryTypographyProps={{
594
+ sx: {
595
+ overflow: "hidden",
596
+ textOverflow: "ellipsis",
597
+ whiteSpace: "nowrap",
598
+ fontSize: 16,
599
+ },
600
+ }}
601
+ />
602
+ </ListItemButton>
603
+ </ListItem>
604
+ );
605
+ })}
606
+ </List>
607
+ </Paper>
608
+ </Stack>
609
+ </Stack>
610
+ <Stack
611
+ sx={{
612
+ flex: 1,
613
+ width: { md: "50%" },
614
+ height: { xs: "50%", md: "auto" },
615
+ position: "relative",
616
+ }}
617
+ >
618
+ <AppBar position="static" elevation={0} color="default">
619
+ <Toolbar>
620
+ <Typography sx={{ mr: 2 }}>Game Preview</Typography>
621
+
622
+ <IconButton
623
+ edge="start"
624
+ color="inherit"
625
+ aria-label="Reload"
626
+ onClick={() => {
627
+ reload();
628
+ }}
629
+ >
630
+ <ReplayIcon />
631
+ </IconButton>
632
+
633
+ <Box sx={{ flex: 1 }} />
634
+
635
+ {current && current.id !== "1" && (
636
+ <>
637
+ <Typography>Share on</Typography>
638
+ <Codesandbox
639
+ title={current.task}
640
+ content={current.content}
641
+ />
642
+ </>
643
+ )}
644
+ </Toolbar>
645
+ </AppBar>
646
+ {loadingLive && (
647
+ <Box
648
+ sx={{
649
+ position: "absolute",
650
+ zIndex: 100,
651
+ top: "50%",
652
+ left: "50%",
653
+ transform: "translate(-50%,-50%)",
654
+ }}
655
+ >
656
+ <CircularProgress />
657
+ </Box>
658
+ )}
659
+ <Box
660
+ ref={ref}
661
+ component="iframe"
662
+ sx={{
663
+ width: "100%",
664
+ flex: 1,
665
+ m: 0,
666
+ border: 0,
667
+ overflow: "hidden",
668
+ visibility: loadingLive ? "hidden" : undefined,
669
+ }}
670
+ onLoad={() => {
671
+ if (current) {
672
+ setLoadingLive(true);
673
+ setTries(1);
674
+ connection.current = false;
675
+ call({ template: current.content });
676
+ }
677
+ }}
678
+ src="/live"
679
+ />
680
+ </Stack>
681
+ </Stack>
682
+ </Paper>
683
+
684
+ <SimpleSnackbar
685
+ handleClose={handleSnackbarClose}
686
+ showError={showError}
687
+ message={errorMessage}
688
+ />
689
+ </>
690
+ );
691
+ }
src/components/Instructions.tsx ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Alert, Link, List, ListItem, ListItemText, Paper, Typography } from "@mui/material";
2
+ import { DividerBox, SectionBox, OutlinedBox } from "@/components/base/boxes";
3
+ import { systemMessage } from "@/constants";
4
+
5
+ export default function Instructions() {
6
+ return (
7
+ <>
8
+ <DividerBox />
9
+
10
+ <SectionBox>
11
+ <Typography component="h3" variant="h2">
12
+ Under the Hood
13
+ </Typography>
14
+ </SectionBox>
15
+
16
+ <Paper>
17
+ <List>
18
+ <ListItem>
19
+ <ListItemText>
20
+ Build on top of{" "}
21
+ <Link
22
+ href="https://huggingface.co/spaces/failfast/nextjs-hf-spaces"
23
+ target="_blank"
24
+ rel="noopener"
25
+ >
26
+ nextjs-hf-spaces
27
+ </Link>{" "}
28
+ with NextJS + TypeScript +{" "}
29
+ <Link
30
+ href="https://github.com/openai/openai-node"
31
+ target="_blank"
32
+ rel="noopener"
33
+ >
34
+ openai-node
35
+ </Link>
36
+ . The full source code can be found on{" "}
37
+ <Link
38
+ href="https://github.com/failfa-st/2D-GameCreator-GPT"
39
+ target="_blank"
40
+ rel="noopener"
41
+ >
42
+ failfa-st/2D-GameCreator-GPT.
43
+ </Link>
44
+ </ListItemText>
45
+ </ListItem>
46
+ <ListItem>
47
+ <ListItemText>Games are stored in localStorage.</ListItemText>
48
+ </ListItem>
49
+ <ListItem>
50
+ <ListItemText>
51
+ We use a very strong <b>system message</b> to make sure that the AI
52
+ behaves like a 2D Game Developer, where <b>&quot;TEMPLATE&quot;</b>{" "}
53
+ refers to the code of the selected game in the{" "}
54
+ <OutlinedBox>Games</OutlinedBox> list, intitally this is the{" "}
55
+ <b>Base Game</b>:
56
+ </ListItemText>
57
+ </ListItem>
58
+ <ListItem>
59
+ <ListItemText>
60
+ <code>
61
+ <pre>{systemMessage}</pre>
62
+ </code>
63
+ </ListItemText>
64
+ </ListItem>
65
+ <ListItem>
66
+ <ListItemText>
67
+ The <b>user message</b> follows another template, to make sure that the
68
+ selected <OutlinedBox>Command</OutlinedBox> is included and that the AI
69
+ always returns the full source code, as this is easier to process
70
+ instead of just parts of the code. The prompt is the message that you
71
+ entered into the <OutlinedBox>prompt</OutlinedBox> field:
72
+ </ListItemText>
73
+ </ListItem>
74
+ <ListItem>
75
+ <ListItemText>
76
+ <code>
77
+ <pre>
78
+ {`"$\{command\}": $\{prompt\}. Return the full source code of the game.
79
+ TEMPLATE:`}
80
+ </pre>
81
+ </code>
82
+ </ListItemText>
83
+ </ListItem>
84
+ </List>
85
+ </Paper>
86
+
87
+ <SectionBox>
88
+ <Typography component="h3" variant="h2">
89
+ Troubleshooting
90
+ </Typography>
91
+ </SectionBox>
92
+
93
+ <Paper sx={{ p: 1 }}>
94
+ <Alert severity="error" sx={{ fontSize: "1.25rem", mb: 1 }}>
95
+ Unhandled Runtime Error: SyntaxError: Unexpected identifier
96
+ </Alert>
97
+
98
+ <Typography variant="body1">
99
+ The generated output was interrupted, as it was too long and the OpenAI API
100
+ delivered not everything. If you can, switch to GPT-4 as it allows a bigger
101
+ context size (change it in the options and also increase the max_tokens). If you
102
+ can&apos;t do this, then please help us extend the GameCreator so that it can
103
+ also resume when the output is interrupted.
104
+ </Typography>
105
+ </Paper>
106
+
107
+ <DividerBox />
108
+
109
+ <Paper sx={{ p: 1 }}>
110
+ <Typography>
111
+ You need help? Something is not working?{" "}
112
+ <Link
113
+ href="https://huggingface.co/spaces/failfast/2D-GameCreator-GPT/discussions"
114
+ target="_blank"
115
+ rel="noopener"
116
+ >
117
+ Please let us know!
118
+ </Link>
119
+ </Typography>
120
+ </Paper>
121
+ </>
122
+ );
123
+ }
src/components/Introduction.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Stack,
3
+ Grid,
4
+ Typography,
5
+ Paper,
6
+ List,
7
+ ListSubheader,
8
+ ListItem,
9
+ ListItemIcon,
10
+ ListItemText,
11
+ Link,
12
+ } from "@mui/material";
13
+ import KeyIcon from "@mui/icons-material/Key";
14
+ import SmartButtonIcon from "@mui/icons-material/SmartButton";
15
+ import PlayArrowIcon from "@mui/icons-material/PlayArrow";
16
+
17
+ export default function Introduction() {
18
+ return (
19
+ <Stack direction="row" spacing={2}>
20
+ <Grid container gap={2} sx={{ justifyContent: "center" }}>
21
+ <Grid item md={4}>
22
+ <Typography sx={{ mb: 1 }}>
23
+ Your job is to provide a prompt that describes the game you want, so that
24
+ your skilled 2D Game Developer can built it for you using JavaScript on
25
+ Canvas2D.
26
+ </Typography>
27
+
28
+ <Typography>
29
+ We would love to use open-source models for this (for example{" "}
30
+ <Link
31
+ href="https://huggingface.co/HuggingFaceH4/starchat-alpha"
32
+ target="_blank"
33
+ rel="noopener"
34
+ >
35
+ starchat-alpha
36
+ </Link>
37
+ ), but running a private Inference Endpoint is too expensive for us. If you
38
+ would love to help us, then{" "}
39
+ <Link
40
+ href="https://discord.com/invite/m3TBB9XEkb"
41
+ target="_blank"
42
+ rel="noopener"
43
+ >
44
+ let&apos;s talk
45
+ </Link>
46
+ !
47
+ </Typography>
48
+ </Grid>
49
+ <Grid item md={4}>
50
+ <Paper>
51
+ <List disablePadding>
52
+ <ListSubheader>Quickstart</ListSubheader>
53
+ <ListItem>
54
+ <ListItemIcon>
55
+ <KeyIcon />
56
+ </ListItemIcon>
57
+ <ListItemText>
58
+ {" "}
59
+ Add your&nbsp;
60
+ <Link
61
+ href="https://platform.openai.com/account/api-keys"
62
+ target="_blank"
63
+ rel="noopener"
64
+ >
65
+ OpenAI API key
66
+ </Link>
67
+ </ListItemText>
68
+ </ListItem>
69
+ <ListItem>
70
+ <ListItemIcon>
71
+ <SmartButtonIcon />
72
+ </ListItemIcon>
73
+ <ListItemText>
74
+ Select one of the <b>examples</b>
75
+ </ListItemText>
76
+ </ListItem>
77
+ <ListItem>
78
+ <ListItemIcon>
79
+ <PlayArrowIcon />
80
+ </ListItemIcon>
81
+ <ListItemText>
82
+ Click on <b>Run</b>
83
+ </ListItemText>
84
+ </ListItem>
85
+ </List>
86
+ </Paper>
87
+ </Grid>
88
+ </Grid>
89
+ </Stack>
90
+ );
91
+ }
src/components/Workflow.tsx ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PlayArrow, Code, Replay, CropSquare } from "@mui/icons-material";
2
+ import {
3
+ Button,
4
+ Grid,
5
+ IconButton,
6
+ List,
7
+ ListItem,
8
+ ListSubheader,
9
+ Paper,
10
+ Typography,
11
+ } from "@mui/material";
12
+ import { OutlinedBox, SectionBox, RainbowBox } from "@/components/base/boxes";
13
+ import { Key } from "react";
14
+
15
+ const workflowSteps = [
16
+ {
17
+ title: "Create a New Game",
18
+ steps: [
19
+ "Input your game concept into the prompt field.",
20
+ "Make sure the command field is set to create game.",
21
+ "Ensure that the base game is selected in the games list.",
22
+ "Click on run to start the game creation process.",
23
+ "Once the game generation is complete, it will be added to the games list and selected automatically.",
24
+ "You can view the newly created game in the game preview section.",
25
+ ],
26
+ },
27
+ {
28
+ title: "Add a New Feature",
29
+ steps: [
30
+ "Clear the prompt field and input the new feature you want to add.",
31
+ "Set the command field to add feature.",
32
+ "Ensure that the previously generated game is selected in the games list.",
33
+ "Click on run to start the feature addition process.",
34
+ "Once the feature addition is complete, it will be added to the games list and selected automatically.",
35
+ "If a bug appears in the process, proceed to the next section.",
36
+ ],
37
+ },
38
+ {
39
+ title: "Fix a Bug",
40
+ steps: [
41
+ "Clear the prompt field and input the bug that you have encountered.",
42
+ "Set the command field to fix bug.",
43
+ "Ensure that the game with the bug is selected in the games list.",
44
+ "Click on run to start the bug fixing process.",
45
+ "Once the bug fixing is complete, it will be reflected in the selected game in the games list.",
46
+ ],
47
+ },
48
+ {
49
+ title: "Extend a Feature",
50
+ steps: [
51
+ "Clear the prompt field and input the changes you want to make to the feature.",
52
+ "Set the command field to extend feature.",
53
+ "Ensure that the game with the feature is selected in the games list.",
54
+ "Click on run to start the feature extension process.",
55
+ "Once the feature extension is complete, it will be reflected in the selected game in the games list.",
56
+ ],
57
+ },
58
+ {
59
+ title: "Start From Scratch",
60
+ steps: [
61
+ "Clear the prompt field and input a new game concept.",
62
+ "Set the command field to create game.",
63
+ "Ensure that the base game is selected in the games list.",
64
+ "Click on run to start the new game creation process.",
65
+ "Once the game generation is complete, it will be added to the games list and selected automatically.",
66
+ "You can view the newly created game in the game preview section.",
67
+ ],
68
+ },
69
+ {
70
+ title: "Viewing & Editing Source Code",
71
+ steps: [
72
+ "To see the source code of the selected game, click the source code button in the header. This will open a code editor where you can manually change the code if desired.",
73
+ "Save changes with CTRL+S using your keyboard.",
74
+ ],
75
+ },
76
+ {
77
+ title: "Refreshing Game Preview",
78
+ steps: [
79
+ "The refresh button in the header of the game preview refreshes the game preview.",
80
+ "Click this if you've made changes and want to see them reflected in the game preview and for what ever reason the game preview didn't refresh automatically.",
81
+ "It's also useful if you are GAME OVER and have not added a feature to restart the game yet.",
82
+ ],
83
+ },
84
+ {
85
+ title: "Exporting to CodeSandbox",
86
+ steps: [
87
+ "To export your game to CodeSandbox, click the share on codesandbox button in the game preview header.",
88
+ "Note: This feature will not work for the base game; it can only export generated games.",
89
+ ],
90
+ },
91
+ {
92
+ title: "Using Options",
93
+ steps: [
94
+ "Open the Options by clicking on them",
95
+ "Switch the model between gpt-4 and gpt-3.5-turbo (default)",
96
+ "Update the max_tokens to 4096 (gpt-4), default is 2048 (gpt-3.5-turbo)",
97
+ "Make changes to the temperature if you want more random results, default is 0.2",
98
+ ],
99
+ },
100
+ ];
101
+
102
+ function parseString(string: string) {
103
+ const keywords = {
104
+ prompt: (item: string, index: Key | null | undefined) => (
105
+ <>
106
+ &nbsp;<OutlinedBox key={index}>{item}</OutlinedBox>&nbsp;
107
+ </>
108
+ ),
109
+ command: (item: string, index: Key | null | undefined) => (
110
+ <>
111
+ &nbsp;<OutlinedBox key={index}>{item}</OutlinedBox>&nbsp;
112
+ </>
113
+ ),
114
+ options: (item: string, index: Key | null | undefined) => (
115
+ <>
116
+ &nbsp;<OutlinedBox key={index}>Options</OutlinedBox>&nbsp;
117
+ </>
118
+ ),
119
+ games: (item: string, index: Key | null | undefined) => (
120
+ <>
121
+ &nbsp;<OutlinedBox key={index}>Games</OutlinedBox>&nbsp;
122
+ </>
123
+ ),
124
+ run: (item: string, index: Key | null | undefined) => (
125
+ <>
126
+ &nbsp;
127
+ <Button variant="contained" startIcon={<PlayArrow />} key={index}>
128
+ <Typography sx={{ fontWeight: "500" }}>Run</Typography>
129
+ </Button>
130
+ &nbsp;
131
+ </>
132
+ ),
133
+ "source code button": (item: string, index: Key | null | undefined) => (
134
+ <>
135
+ &nbsp;
136
+ <IconButton color="inherit" aria-label={"Show Code"}>
137
+ <Code />
138
+ </IconButton>
139
+ button&nbsp;
140
+ </>
141
+ ),
142
+ "refresh button": (item: string, index: Key | null | undefined) => (
143
+ <>
144
+ &nbsp;
145
+ <IconButton color="inherit" aria-label={"Refresh"}>
146
+ <Replay />
147
+ </IconButton>
148
+ button&nbsp;
149
+ </>
150
+ ),
151
+ "codesandbox button": (item: string, index: Key | null | undefined) => (
152
+ <>
153
+ &nbsp;
154
+ <IconButton color="inherit" aria-label={"Save to codesandbox"}>
155
+ <CropSquare />
156
+ </IconButton>
157
+ button&nbsp;
158
+ </>
159
+ ),
160
+ "game preview": (item: string, index: Key | null | undefined) => (
161
+ <b key={index}>&nbsp;Game Preview&nbsp;</b>
162
+ ),
163
+ "base game": (item: string, index: Key | null | undefined) => (
164
+ <b key={index}>&nbsp;Base Game&nbsp;</b>
165
+ ),
166
+ "create game": (item: string, index: Key | null | undefined) => (
167
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
168
+ ),
169
+ "add feature": (item: string, index: Key | null | undefined) => (
170
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
171
+ ),
172
+ "remove feature": (item: string, index: Key | null | undefined) => (
173
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
174
+ ),
175
+ "extend feature": (item: string, index: Key | null | undefined) => (
176
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
177
+ ),
178
+ "fix bug": (item: string, index: Key | null | undefined) => (
179
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
180
+ ),
181
+ model: (item: string, index: Key | null | undefined) => (
182
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
183
+ ),
184
+ max_tokens: (item: string, index: Key | null | undefined) => (
185
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
186
+ ),
187
+ temperature: (item: string, index: Key | null | undefined) => (
188
+ <b key={index}>&nbsp;{item.trim()}&nbsp;</b>
189
+ ),
190
+ };
191
+
192
+ Object.keys(keywords).forEach(keyword => {
193
+ const regex = new RegExp(`\\s*\\b${keyword}\\b\\s*`, "gi");
194
+ string = string.replace(regex, `|${keyword}|`);
195
+ });
196
+
197
+ const items = string.split("|").filter(item => item !== "");
198
+
199
+ return items.map((item, index) => {
200
+ if (keywords.hasOwnProperty(item.trim())) {
201
+ return keywords[item.trim() as keyof typeof keywords](item.trim(), index);
202
+ } else {
203
+ return item;
204
+ }
205
+ });
206
+ }
207
+
208
+ export default function Workflow() {
209
+ return (
210
+ <>
211
+ <SectionBox>
212
+ <Typography component="h3" variant="h2">
213
+ How to use?
214
+ </Typography>
215
+ </SectionBox>
216
+
217
+ <Grid container spacing={2}>
218
+ {workflowSteps.map(({ title, steps }, index) => {
219
+ return (
220
+ <Grid item key={index} md={4}>
221
+ <Paper>
222
+ <List disablePadding>
223
+ <RainbowBox sx={{ mb: 2 }}>
224
+ <ListSubheader>{title}</ListSubheader>
225
+ </RainbowBox>
226
+
227
+ {steps.map((step, index) => {
228
+ return (
229
+ <ListItem key={index}>
230
+ <Typography>
231
+ <>{parseString(step)}</>
232
+ </Typography>
233
+ </ListItem>
234
+ );
235
+ })}
236
+ </List>
237
+ </Paper>
238
+ </Grid>
239
+ );
240
+ })}
241
+ </Grid>
242
+ </>
243
+ );
244
+ }
src/components/{ExampleButton.tsx → base/ExampleButton.tsx} RENAMED
File without changes
src/components/base/boxes.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Divider, DividerProps, Paper, PaperProps } from "@mui/material";
2
+ import { styled } from "@mui/material/styles";
3
+
4
+ export const SectionBox = styled(Paper)<PaperProps>(({ theme }) => ({
5
+ display: "flex",
6
+ padding: 15,
7
+ paddingTop: 30,
8
+ paddingBottom: 30,
9
+ marginBottom: 20,
10
+ background: `linear-gradient(to bottom right, ${theme.palette.primary.main} 0%, ${theme.palette.secondary.main} 100%)`,
11
+ }));
12
+
13
+ export const HighlightBox = styled(Paper)<PaperProps>(({ theme }) => ({
14
+ display: "flex",
15
+ alignItems: "center",
16
+ justifyContent: "center",
17
+ padding: 10,
18
+ borderBottom: `3px solid transparent`,
19
+ borderImage: `linear-gradient(to bottom right, #b827fc 0%, #2c90fc 25%, #b8fd33 50%, #fec837 75%, #fd1892 100%)`,
20
+ borderImageSlice: 1,
21
+ }));
22
+
23
+ export const RainbowBox = styled("div")(({ theme }) => ({
24
+ border: `1px solid transparent`,
25
+ borderImage: `linear-gradient(to bottom right, #b827fc 0%, #2c90fc 25%, #b8fd33 50%, #fec837 75%, #fd1892 100%)`,
26
+ borderImageSlice: 1,
27
+ }));
28
+
29
+ export const DividerBox = styled(Divider)<DividerProps>(({ theme }) => ({
30
+ marginTop: 20,
31
+ marginBottom: 20,
32
+ background: "transparent",
33
+ border: "none",
34
+ }));
35
+
36
+ export const MarkerBox = styled("span")(({ theme }) => ({
37
+ padding: 2,
38
+ background: `linear-gradient(to bottom right, ${theme.palette.primary.main} 0%, ${theme.palette.secondary.main} 100%)`,
39
+ }));
40
+
41
+ export const OutlinedBox = styled("span")(({ theme }) => ({
42
+ ...theme.typography.body1,
43
+ padding: theme.spacing(0.25),
44
+ border: `1px solid ${theme.palette.grey[800]}`,
45
+ borderRadius: theme.shape.borderRadius,
46
+ transition: theme.transitions.create(["border-color", "box-shadow"]),
47
+ }));
src/components/base/secret.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ IconButton,
3
+ InputAdornment,
4
+ TextField,
5
+ TextFieldProps,
6
+ } from "@mui/material";
7
+ import { useState } from "react";
8
+ import { Visibility, VisibilityOff } from "@mui/icons-material";
9
+
10
+ export default function Secret(props: TextFieldProps) {
11
+ const { name = "secret", label = "Secret" } = props;
12
+ const [showSecret, setShowSecret] = useState(false);
13
+
14
+ const handleShowSecret = () => setShowSecret(!showSecret);
15
+
16
+ return (
17
+ <TextField
18
+ variant="filled"
19
+ label={label}
20
+ name={name}
21
+ type={showSecret ? "text" : "password"}
22
+ InputProps={{
23
+ endAdornment: (
24
+ <InputAdornment position="end">
25
+ <IconButton onClick={handleShowSecret}>
26
+ {showSecret ? <Visibility /> : <VisibilityOff />}
27
+ </IconButton>
28
+ </InputAdornment>
29
+ ),
30
+ }}
31
+ />
32
+ );
33
+ }
src/components/footer.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Box from "@mui/material/Box";
2
+ import Image from "next/image";
3
+ import { Divider, Link } from "@mui/material";
4
+
5
+ const Footer = () => {
6
+ return (
7
+ <Box
8
+ component="footer"
9
+ sx={{
10
+ display: "flex",
11
+ justifyContent: "center",
12
+ gap: 1,
13
+ alignItems: "center",
14
+ mt: 8,
15
+ mb: 4,
16
+ }}
17
+ >
18
+ <Link
19
+ href="https://failfa.st"
20
+ display="flex"
21
+ alignItems="center"
22
+ rel="noopener"
23
+ target="_blank"
24
+ >
25
+ <Box sx={{ mr: 0.5 }}>Powered by</Box>{" "}
26
+ <Image src="/failfast.svg" alt="failfast Logo" width="32" height="32" />
27
+ </Link>
28
+ </Box>
29
+ );
30
+ };
31
+
32
+ export default Footer;
src/components/title.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button, Link, Paper, Stack, Typography } from "@mui/material";
2
+ import { HighlightBox } from "./base/boxes";
3
+ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
4
+
5
+ export default function Title() {
6
+ return (
7
+ <Stack
8
+ spacing={4}
9
+ sx={{
10
+ justifyContent: "center",
11
+ alignItems: "center",
12
+ minHeight: "40vh",
13
+ p: 4,
14
+ }}
15
+ >
16
+ <Typography variant="h1" component="h1">
17
+ 2D GameCreator-GPT
18
+ </Typography>
19
+
20
+ <HighlightBox>
21
+ <Typography variant="h5" component="p">
22
+ text-to-game using OpenAI GPT 3.5 / GPT 4
23
+ </Typography>
24
+ </HighlightBox>
25
+
26
+ <Stack gap={2} direction="row">
27
+ <Link
28
+ href="https://discord.com/invite/m3TBB9XEkb"
29
+ target="_blank"
30
+ rel="noopener"
31
+ sx={{ alignSelf: "end" }}
32
+ >
33
+ <img src="https://img.shields.io/discord/1091306623819059300?color=7289da&label=Discord&logo=discord&logoColor=fff&style=for-the-badge" />
34
+ </Link>
35
+
36
+ <Button
37
+ href="https://github.com/failfa-st/nextjs-docker-starter"
38
+ target="_blank"
39
+ rel="noopener"
40
+ >
41
+ Contribute on GitHub
42
+ </Button>
43
+ </Stack>
44
+ </Stack>
45
+ );
46
+ }
src/constants/baseGame.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const baseGame = {
2
+ default: `const canvas = document.querySelector('canvas');
3
+ canvas.style.backgroundColor = '#fff';
4
+ const ctx = canvas.getContext('2d');
5
+ const w = canvas.width;
6
+ const h = canvas.height;
7
+
8
+ function draw(){
9
+ const FPS = 60;
10
+
11
+ // TODO: Add drawing logic here
12
+
13
+ // NEVER stop the loop
14
+ setTimeout(requestAnimationFrame(draw),1000/FPS)
15
+ }
16
+ draw();
17
+ `.trim(),
18
+ };
src/constants/index.ts CHANGED
@@ -1,22 +1,28 @@
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
- };
17
 
18
- export const COMMAND_CREATE_GAME = "CREATE_GAME";
19
- export const COMMAND_ADD_FEATURE = "ADD";
20
- export const COMMAND_REMOVE_FEATURE = "REMOVE";
21
- export const COMMAND_EXTEND_FEATURE = "EXTEND";
22
- export const COMMAND_FIX_BUG = "FIX";
 
 
 
1
+ export const COMMAND_CREATE_GAME = "CREATE_GAME";
2
+ export const COMMAND_ADD_FEATURE = "ADD_FEATURE";
3
+ export const COMMAND_REMOVE_FEATURE = "REMOVE_FEATURE";
4
+ export const COMMAND_EXTEND_FEATURE = "UPDATE_FEATURE";
5
+ export const COMMAND_FIX_BUG = "FIX_BUG";
6
 
7
+ export const COMMAND_LABEL_CREATE_GAME = "create game";
8
+ export const COMMAND_LABEL_ADD_FEATURE = "add feature";
9
+ export const COMMAND_LABEL_REMOVE_FEATURE = "remove feature";
10
+ export const COMMAND_LABEL_EXTEND_FEATURE = "extend feature";
11
+ export const COMMAND_LABEL_FIX_BUG = "fix bug";
12
 
13
+ export const systemMessage = `You are a skilled 2D game developer working with JavaScript on Canvas2D, aim for high performance
14
+ You understand and follow a set of "COMMANDS" to build games:
15
 
16
+ "${COMMAND_CREATE_GAME}": Initiate the development. Consider the game type, environment, basic mechanics and extend the "TEMPLATE"
17
+ "${COMMAND_ADD_FEATURE}": Add the new feature
18
+ "${COMMAND_REMOVE_FEATURE}": Remove the existing feature
19
+ "${COMMAND_EXTEND_FEATURE}": Modify an existing feature, altering its behavior or properties
20
+ "${COMMAND_FIX_BUG}": Debug and fix problems, ensuring edverything functions as intended
 
21
 
22
+ NEVER use any external assets: image, base64, sprite, audio, svg
23
+ NEVER use alert! Write your message on Canvas directly
24
+ NEVER add comments to reduce the token amount
25
+ You can choose from these global libs: Mousetrap.bind, confetti
26
+ Your "OUTPUT FORMAT" must be valid JavaScript code within a markdown code block
27
+ It's crucial to follow the "COMMANDS" and "OUTPUT FORMAT" for the desired results
28
+ `;
src/lib/theme.ts CHANGED
@@ -1,5 +1,5 @@
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"],
@@ -13,20 +13,23 @@ const theme = extendTheme({
13
  light: {
14
  palette: {
15
  primary: {
16
- main: "#40088d",
17
  },
18
  secondary: {
19
- main: "#038225",
20
  },
21
  },
22
  },
23
  dark: {
24
  palette: {
25
  primary: {
26
- main: "#00d720",
27
  },
28
  secondary: {
29
- main: "#cc06ed",
 
 
 
30
  },
31
  },
32
  },
@@ -34,28 +37,26 @@ const theme = extendTheme({
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
  });
 
1
  import { Fira_Code, Poppins } from "next/font/google";
2
+ import { experimental_extendTheme as extendTheme, Theme } from "@mui/material/styles";
3
 
4
  export const poppins = Poppins({
5
  weight: ["300", "400", "500", "700"],
 
13
  light: {
14
  palette: {
15
  primary: {
16
+ main: "#2c90fc",
17
  },
18
  secondary: {
19
+ main: "#b827fc",
20
  },
21
  },
22
  },
23
  dark: {
24
  palette: {
25
  primary: {
26
+ main: "#2c90fc",
27
  },
28
  secondary: {
29
+ main: "#b827fc",
30
+ },
31
+ text: {
32
+ secondary: "#ffffff",
33
  },
34
  },
35
  },
 
37
  typography: {
38
  ...poppins.style,
39
  h1: {
40
+ fontSize: "5em",
 
 
 
 
 
 
 
 
 
41
  },
42
+ },
43
+ components: {
44
+ MuiLink: {
45
+ styleOverrides: {
46
+ root: {
47
+ textDecoration: "none",
48
+ ":hover": {
49
+ textDecoration: "underline",
50
+ },
51
+ },
52
+ },
53
  },
54
+ MuiListSubheader: {
55
+ styleOverrides: {
56
+ root: {
57
+ fontSize: "1.35rem",
58
+ },
59
+ },
60
  },
61
  },
62
  });
src/pages/index.tsx CHANGED
@@ -1,659 +1,35 @@
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";
7
- import ClearIcon from "@mui/icons-material/Clear";
8
- import CodeIcon from "@mui/icons-material/Code";
9
- import CodeOffIcon from "@mui/icons-material/CodeOff";
10
- import EditIcon from "@mui/icons-material/Edit";
11
- import VisibilityIcon from "@mui/icons-material/Visibility";
12
- import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
13
- import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
14
- import PlayArrowIcon from "@mui/icons-material/PlayArrow";
15
- import ReplayIcon from "@mui/icons-material/Replay";
16
- import MoneyIcon from "@mui/icons-material/Money";
17
- import TollIcon from "@mui/icons-material/Toll";
18
- import TextField from "@mui/material/TextField";
19
- import Box from "@mui/material/Box";
20
  import Stack from "@mui/material/Stack";
21
- import Accordion from "@mui/material/Accordion";
22
- import Typography from "@mui/material/Typography";
23
- import AccordionSummary from "@mui/material/AccordionSummary";
24
- import AccordionDetails from "@mui/material/AccordionDetails";
25
- import Paper from "@mui/material/Paper";
26
- import IconButton from "@mui/material/IconButton";
27
- import List from "@mui/material/List";
28
- import ListItem from "@mui/material/ListItem";
29
- import { nanoid } from "nanoid";
30
- import AppBar from "@mui/material/AppBar";
31
- import Toolbar from "@mui/material/Toolbar";
32
- import ListItemIcon from "@mui/material/ListItemIcon";
33
- import ListItemButton from "@mui/material/ListItemButton";
34
- import ListItemText from "@mui/material/ListItemText";
35
- import { useHost } from "esdeka/react";
36
- import CircularProgress from "@mui/material/CircularProgress";
37
  import CssBaseline from "@mui/material/CssBaseline";
38
- import Slider from "@mui/material/Slider";
39
- import { useAtom } from "jotai";
40
- import Button from "@mui/material/Button";
41
- import dynamic from "next/dynamic";
42
- 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 { useColorScheme } from "@mui/material/styles";
47
- import { getTheme, prettify } from "@/utils";
48
- import { answersAtom, showCodeAtom } from "@/store/atoms";
49
- import {
50
- COMMAND_ADD_FEATURE,
51
- COMMAND_CREATE_GAME,
52
- COMMAND_EXTEND_FEATURE,
53
- COMMAND_FIX_BUG,
54
- COMMAND_REMOVE_FEATURE,
55
- base,
56
- } from "@/constants";
57
- import { EditTitle } from "@/components/EditTitle";
58
- import Link from "next/link";
59
- import { fontMono } from "@/lib/theme";
60
- import { Codesandbox } from "@/components/Codesandbox";
61
- import { Codepen } from "@/components/Codepen";
62
- import { InfoMenu } from "@/components/InfoMenu";
63
- import SimpleSnackbar from "@/components/SimpleSnackbar";
64
- import ExampleButton from "@/components/ExampleButton";
65
- import { ListSubheader } from "@mui/material";
66
- const MonacoEditor = dynamic(import("@monaco-editor/react"), { ssr: false });
67
-
68
- export interface ShareProps {
69
- title: string;
70
- content: string;
71
- }
72
 
73
  export default function Home() {
74
- const ref = useRef<HTMLIFrameElement>(null);
75
- const [prompt, setPrompt] = useState("");
76
- const [template, setTemplate] = useState(prettify(base.default));
77
- const [runningId, setRunningId] = useState("1");
78
- const [activeId, setActiveId] = useState("1");
79
- const [editingId, setEditingId] = useState<string | null>(null);
80
- const [answers, setAnswers] = useAtom(answersAtom);
81
- const [showCode, setShowCode] = useAtom(showCodeAtom);
82
- const [loading, setLoading] = useState(false);
83
- const [loadingLive, setLoadingLive] = useState(true);
84
- const [showError, setShowError] = useState(false);
85
- const [errorMessage, setErrorMessage] = useState("");
86
-
87
- const { mode, systemMode } = useColorScheme();
88
-
89
- const { call, subscribe } = useHost(ref, "2DGameGPT");
90
-
91
- const connection = useRef(false);
92
- const [tries, setTries] = useState(1);
93
-
94
- // Send a connection request
95
- useEffect(() => {
96
- const current = answers.find(({ id }) => id === runningId);
97
- if (connection.current || tries <= 0) {
98
- return () => {
99
- /* Consistency */
100
- };
101
- }
102
-
103
- const timeout = setTimeout(() => {
104
- if (current) {
105
- call({ template: current.content });
106
- }
107
-
108
- setTries(tries - 1);
109
- }, 1_000);
110
-
111
- return () => {
112
- clearTimeout(timeout);
113
- };
114
- }, [call, tries, answers, runningId]);
115
-
116
- useEffect(() => {
117
- if (!connection.current && loadingLive) {
118
- const unsubscribe = subscribe(event => {
119
- const { action } = event.data;
120
- switch (action.type) {
121
- case "answer":
122
- connection.current = true;
123
- setLoadingLive(false);
124
- break;
125
- default:
126
- break;
127
- }
128
- });
129
- return () => {
130
- unsubscribe();
131
- };
132
- }
133
- return () => {
134
- /* Consistency */
135
- };
136
- }, [subscribe, loadingLive]);
137
-
138
- const current = answers.find(({ id }) => id === activeId);
139
-
140
- const handleSnackbarClose = (event: SyntheticEvent | Event, reason?: string) => {
141
- if (reason === "clickaway") {
142
- return;
143
- }
144
-
145
- setShowError(false);
146
- };
147
-
148
- function reload() {
149
- connection.current = false;
150
- if (ref.current) {
151
- ref.current.src = `/live?${nanoid()}`;
152
- setLoadingLive(true);
153
- setTries(1);
154
- }
155
- }
156
-
157
  return (
158
  <>
159
  <CssBaseline />
160
- <Stack
161
- sx={{
162
- ...fontMono.style,
163
- position: "absolute",
164
- inset: 0,
165
- overflow: "hidden",
166
- flexDirection: { md: "row" },
167
- height: "100%",
168
- }}
169
- >
170
- <Stack
171
- sx={{
172
- width: { md: "50%" },
173
- height: { xs: "50%", md: "100%" },
174
- flex: 1,
175
- overflow: "hidden",
176
- }}
177
- >
178
- <AppBar position="static" elevation={0} color="default">
179
- <Toolbar>
180
- <Typography variant="h3" component="h1" sx={{ flex: 1, m: 0 }}>
181
- 2D GameCreator-GPT
182
- </Typography>
183
-
184
- <IconButton
185
- color="inherit"
186
- aria-label={showCode ? "Hide Code" : "Show Code"}
187
- onClick={() => {
188
- setShowCode(previousState => !previousState);
189
- }}
190
- >
191
- {showCode ? <CodeOffIcon /> : <CodeIcon />}
192
- </IconButton>
193
- <IconButton
194
- edge="end"
195
- color="inherit"
196
- aria-label="Clear Prompt"
197
- onClick={async () => {
198
- setActiveId("1");
199
- setRunningId("1");
200
- setTemplate(prettify(base.default));
201
- reload();
202
- }}
203
- >
204
- <ClearIcon />
205
- </IconButton>
206
- </Toolbar>
207
- </AppBar>
208
- {showCode && (
209
- <Box
210
- sx={{ flex: 1 }}
211
- onKeyDown={event => {
212
- if (event.key === "s" && event.metaKey) {
213
- event.preventDefault();
214
- setAnswers(previousAnswers =>
215
- previousAnswers.map(previousAnswer => {
216
- return previousAnswer.id === activeId
217
- ? { ...previousAnswer, content: template }
218
- : previousAnswer;
219
- })
220
- );
221
- setTemplate(previousState => prettify(previousState));
222
- reload();
223
- }
224
- }}
225
- >
226
- <MonacoEditor
227
- theme={getTheme(mode, systemMode)}
228
- language="javascript"
229
- value={template}
230
- options={{
231
- fontSize: 14,
232
- }}
233
- onChange={async value => {
234
- setTemplate(value ?? "");
235
- }}
236
- />
237
- </Box>
238
- )}
239
- <Stack
240
- sx={{
241
- flex: 1,
242
- display: showCode ? "none" : undefined,
243
- overflow: "hidden",
244
- }}
245
- >
246
- <Box
247
- component="form"
248
- id="gpt-form"
249
- sx={{ p: 1, pt: 0 }}
250
- onSubmit={async event => {
251
- event.preventDefault();
252
- const formData = new FormData(event.target as HTMLFormElement);
253
- const formObject = Object.fromEntries(formData);
254
- try {
255
- setLoading(true);
256
- const { data } = await axios.post("/api/generate", formObject);
257
- const answer = data;
258
- setAnswers(previousAnswers => [answer, ...previousAnswers]);
259
- setRunningId(answer.id);
260
- setActiveId(answer.id);
261
- setTemplate(prettify(answer.content));
262
- reload();
263
- } catch (error) {
264
- setShowError(true);
265
- setErrorMessage((error as AxiosError).message);
266
- console.error(error);
267
- } finally {
268
- setLoading(false);
269
- }
270
- }}
271
- >
272
- <Paper variant="outlined" sx={{ p: 0 }}>
273
- <Stack sx={{ p: 2, gap: 2 }}>
274
- <Stack direction="row" spacing={1}>
275
- <TextField
276
- multiline
277
- fullWidth
278
- required
279
- id="prompt"
280
- name="prompt"
281
- label="Prompt"
282
- value={prompt}
283
- onChange={e => setPrompt(e.target.value)}
284
- minRows={5}
285
- InputProps={{
286
- style: fontMono.style,
287
- }}
288
- />
289
-
290
- <Stack spacing={1}>
291
- <FormControl variant="outlined" sx={{ minWidth: 180 }}>
292
- <InputLabel id="gpt-command-select-label">
293
- Command
294
- </InputLabel>
295
- <Select
296
- labelId="gpt-command-select-label"
297
- id="gpt-command-select"
298
- name="command"
299
- defaultValue={COMMAND_CREATE_GAME}
300
- label="Command"
301
- >
302
- <MenuItem value={COMMAND_CREATE_GAME}>
303
- create game
304
- </MenuItem>
305
- <MenuItem value={COMMAND_ADD_FEATURE}>
306
- add feature
307
- </MenuItem>
308
- <MenuItem value={COMMAND_REMOVE_FEATURE}>
309
- remove feature
310
- </MenuItem>
311
- <MenuItem value={COMMAND_EXTEND_FEATURE}>
312
- update feature
313
- </MenuItem>
314
- <MenuItem value={COMMAND_FIX_BUG}>
315
- fix bug
316
- </MenuItem>
317
- </Select>
318
- </FormControl>
319
-
320
- <Button
321
- form="gpt-form"
322
- type="submit"
323
- variant="contained"
324
- fullWidth
325
- aria-label={loading ? "Loading" : "Run"}
326
- aria-disabled={loading}
327
- disabled={loading}
328
- startIcon={
329
- loading ? (
330
- <CircularProgress size={20} />
331
- ) : (
332
- <PlayArrowIcon />
333
- )
334
- }
335
- sx={{ pl: 5, pr: 5, flexGrow: 1, overflow: "auto" }}
336
- >
337
- <Typography sx={{ fontWeight: "500" }}>
338
- Run
339
- </Typography>
340
- </Button>
341
- </Stack>
342
- </Stack>
343
-
344
- <Stack direction="row" spacing={1} alignItems="center">
345
- <Typography>Examples</Typography>
346
-
347
- <ExampleButton
348
- title={"Space Invaders"}
349
- text={"Retro Space Invaders"}
350
- onClick={setPrompt}
351
- />
352
 
353
- <ExampleButton
354
- title="Jump & Run"
355
- text={
356
- "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."
357
- }
358
- onClick={setPrompt}
359
- />
360
 
361
- <ExampleButton
362
- title="Flappy Bird"
363
- text={
364
- "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. The pipes have a huge opening so that the bird can easily fly through. 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."
365
- }
366
- onClick={setPrompt}
367
- />
368
-
369
- <ExampleButton
370
- title="Asteroids"
371
- text={
372
- "Asteroids. Control spaceship-movement using arrow keys. Fire bullets with space key to destroy asteroids, breaking them into smaller pieces. Earn points for destroying asteroids, with higher scores for smaller ones. Collision detection when spaceship hits asteroid, collision reduces spaceship health, game over when health is 0."
373
- }
374
- // text={
375
- // "Asteroids. Space ship can fly around in space using the arrow keys. Irregular shaped objects called asteroids flying around the space ship. The space ship can shoot bullets using the space key. When a bullet hits an asteroid, it splits in smaller irregular shaped objects; when asteroid is completely destroyed, the player scores one point. When the space ship collides with an asteroid, it looses 1 health; when the health is 0, the game is over. Restart the game via pressing space key."
376
- // }
377
- onClick={setPrompt}
378
- />
379
- </Stack>
380
- </Stack>
381
- </Paper>
382
-
383
- <Paper variant="outlined" sx={{ mt: 2 }}>
384
- <Accordion disableGutters square elevation={0}>
385
- <AccordionSummary
386
- expandIcon={<ExpandMoreIcon />}
387
- aria-controls="gtp-options-content"
388
- id="gtp-options-header"
389
- sx={{
390
- bgcolor: "background.paper",
391
- color: "text.primary",
392
- }}
393
- >
394
- <Typography>Options</Typography>
395
- </AccordionSummary>
396
- <AccordionDetails>
397
- <Stack gap={2}>
398
- <TextField
399
- fullWidth
400
- multiline
401
- required={process.env.NODE_ENV === "production"}
402
- id="openAPIKey"
403
- name="openAPIKey"
404
- label="OpenAI API Key"
405
- minRows={1}
406
- InputProps={{
407
- style: fontMono.style,
408
- }}
409
- />
410
- <FormControl
411
- fullWidth
412
- variant="outlined"
413
- sx={{ mb: 3 }}
414
- >
415
- <InputLabel id="gpt-model-select-label">
416
- Model
417
- </InputLabel>
418
- <Select
419
- labelId="gpt-model-select-label"
420
- id="gpt-model-select"
421
- name="model"
422
- defaultValue="gpt-3.5-turbo"
423
- label="Model"
424
- >
425
- <MenuItem value="gpt-3.5-turbo">
426
- GPT 3.5 turbo
427
- </MenuItem>
428
- <MenuItem value="gpt-4">GPT 4</MenuItem>
429
- </Select>
430
- </FormControl>
431
- </Stack>
432
- <Stack
433
- spacing={2}
434
- direction="row"
435
- sx={{ mb: 2 }}
436
- alignItems="center"
437
- >
438
- <AcUnitIcon />
439
- <Slider
440
- marks
441
- id="temperature"
442
- name="temperature"
443
- min={0}
444
- max={0.8}
445
- defaultValue={0.2}
446
- step={0.1}
447
- valueLabelDisplay="auto"
448
- aria-label="Temperature"
449
- />
450
- <LocalFireDepartmentIcon />
451
- </Stack>
452
- <Stack
453
- spacing={2}
454
- direction="row"
455
- sx={{ mb: 2 }}
456
- alignItems="center"
457
- >
458
- <TollIcon />
459
- <Slider
460
- marks
461
- id="maxTokens"
462
- name="maxTokens"
463
- min={1024}
464
- max={4096}
465
- defaultValue={2048}
466
- step={256}
467
- valueLabelDisplay="auto"
468
- aria-label="Max Tokens"
469
- />
470
- <MoneyIcon />
471
- </Stack>
472
- <input
473
- id="template"
474
- name="template"
475
- type="hidden"
476
- value={template}
477
- onChange={event => {
478
- setTemplate(event.target.value);
479
- }}
480
- />
481
- </AccordionDetails>
482
- </Accordion>
483
- </Paper>
484
- </Box>
485
-
486
- <Paper variant="elevation" sx={{ p: 1, overflow: "auto" }}>
487
- <List sx={{ flex: 1, p: 0 }}>
488
- <ListSubheader
489
- sx={{ fontSize: "1em", fontWeight: "normal", color: "white" }}
490
- >
491
- Games
492
- </ListSubheader>
493
- {answers.map((answer, index) => {
494
- return (
495
- <ListItem
496
- key={answer.id}
497
- secondaryAction={
498
- <Stack sx={{ flexDirection: "row", gap: 1 }}>
499
- {answer.id === "1" ? undefined : (
500
- <IconButton
501
- edge="end"
502
- aria-label="Delete"
503
- onClick={() => {
504
- setAnswers(previousAnswers =>
505
- previousAnswers.filter(
506
- ({ id }) => id !== answer.id
507
- )
508
- );
509
- if (runningId === answer.id) {
510
- const previous =
511
- answers[index + 1];
512
- if (previous) {
513
- setActiveId(previous.id);
514
- setRunningId(previous.id);
515
- setTemplate(
516
- prettify(
517
- previous.content
518
- )
519
- );
520
- reload();
521
- }
522
- }
523
- }}
524
- >
525
- <DeleteForeverIcon />
526
- </IconButton>
527
- )}
528
- </Stack>
529
- }
530
- disablePadding
531
- >
532
- <ListItemButton
533
- dense
534
- selected={activeId === answer.id}
535
- // disabled={activeId === answer.id}
536
- role={undefined}
537
- onClick={() => {
538
- setActiveId(answer.id);
539
- setRunningId(answer.id);
540
- setTemplate(prettify(answer.content));
541
- reload();
542
- }}
543
- >
544
- <ListItemIcon>
545
- {runningId === answer.id ? (
546
- <CheckIcon />
547
- ) : (
548
- <VisibilityIcon />
549
- )}
550
- </ListItemIcon>
551
-
552
- <ListItemText
553
- primary={answer.task}
554
- primaryTypographyProps={{
555
- sx: {
556
- overflow: "hidden",
557
- textOverflow: "ellipsis",
558
- whiteSpace: "nowrap",
559
- fontSize: 16,
560
- },
561
- }}
562
- />
563
- </ListItemButton>
564
- </ListItem>
565
- );
566
- })}
567
- </List>
568
- </Paper>
569
- </Stack>
570
  </Stack>
571
- <Stack
572
- sx={{
573
- flex: 1,
574
- width: { md: "50%" },
575
- height: { xs: "50%", md: "auto" },
576
- position: "relative",
577
- }}
578
- >
579
- <AppBar position="static" elevation={0} color="default">
580
- <Toolbar>
581
- <IconButton
582
- edge="start"
583
- color="inherit"
584
- aria-label="Reload"
585
- onClick={() => {
586
- reload();
587
- }}
588
- >
589
- <ReplayIcon />
590
- </IconButton>
591
- {current && current.id !== "1" && (
592
- <>
593
- <Codepen title={current.task} content={current.content} />
594
- <Codesandbox title={current.task} content={current.content} />
595
- </>
596
- )}
597
- <Box sx={{ flex: 1 }} />
598
 
599
- <InfoMenu />
600
 
601
- <Link href="/" aria-label="home" style={{ color: "inherit" }}>
602
- <Box
603
- component="svg"
604
- viewBox="0 0 24 24"
605
- sx={{ fontSize: "2em", height: "1em", width: "1em" }}
606
- >
607
- <path
608
- fill="currentColor"
609
- 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"
610
- />
611
- </Box>
612
- </Link>
613
- </Toolbar>
614
- </AppBar>
615
- {loadingLive && (
616
- <Box
617
- sx={{
618
- position: "absolute",
619
- zIndex: 100,
620
- top: "50%",
621
- left: "50%",
622
- transform: "translate(-50%,-50%)",
623
- }}
624
- >
625
- <CircularProgress />
626
- </Box>
627
- )}
628
- <Box
629
- ref={ref}
630
- component="iframe"
631
- sx={{
632
- width: "100%",
633
- flex: 1,
634
- m: 0,
635
- border: 0,
636
- overflow: "hidden",
637
- visibility: loadingLive ? "hidden" : undefined,
638
- }}
639
- onLoad={() => {
640
- if (current) {
641
- setLoadingLive(true);
642
- setTries(1);
643
- connection.current = false;
644
- call({ template: current.content });
645
- }
646
- }}
647
- src="/live"
648
- />
649
- </Stack>
650
- </Stack>
651
 
652
- <SimpleSnackbar
653
- handleClose={handleSnackbarClose}
654
- showError={showError}
655
- message={errorMessage}
656
- />
657
  </>
658
  );
659
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import Stack from "@mui/material/Stack";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import CssBaseline from "@mui/material/CssBaseline";
3
+ import { Container } from "@mui/material";
4
+ import Footer from "@/components/footer";
5
+ import Title from "@/components/title";
6
+ import Introduction from "@/components/Introduction";
7
+ import Instructions from "@/components/Instructions";
8
+ import Examples from "@/components/Examples";
9
+ import GameCreator from "@/components/GameCreator";
10
+ import Workflow from "@/components/Workflow";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  export default function Home() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  return (
14
  <>
15
  <CssBaseline />
16
+ <Container sx={{ maxWidth: { xl: "1536px", md: "100%" } }}>
17
+ <Title />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ <Stack gap={5}>
20
+ <Introduction />
 
 
 
 
 
21
 
22
+ <GameCreator />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  </Stack>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ <Examples />
26
 
27
+ <Workflow />
28
+
29
+ <Instructions />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ <Footer />
32
+ </Container>
 
 
 
33
  </>
34
  );
35
  }
src/pages/live.tsx CHANGED
@@ -1,6 +1,8 @@
1
  import Script from "next/script";
2
- import TWEEN from "@tweenjs/tween.js";
3
  import Mousetrap from "mousetrap";
 
 
 
4
 
5
  const styles = (
6
  <style>
@@ -23,6 +25,11 @@ const styles = (
23
  </style>
24
  );
25
  export default function Page() {
 
 
 
 
 
26
  return (
27
  <>
28
  {styles}
 
1
  import Script from "next/script";
 
2
  import Mousetrap from "mousetrap";
3
+ import confetti from "canvas-confetti";
4
+
5
+ import { useEffect } from "react";
6
 
7
  const styles = (
8
  <style>
 
25
  </style>
26
  );
27
  export default function Page() {
28
+ useEffect(() => {
29
+ window.Mousetrap = Mousetrap;
30
+ window.confetti = confetti;
31
+ }, []);
32
+
33
  return (
34
  <>
35
  {styles}
src/services/api/index.ts CHANGED
@@ -1,14 +1,8 @@
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
- import {
6
- COMMAND_ADD_FEATURE,
7
- COMMAND_CREATE_GAME,
8
- COMMAND_EXTEND_FEATURE,
9
- COMMAND_FIX_BUG,
10
- COMMAND_REMOVE_FEATURE,
11
- } from "@/constants";
12
 
13
  export async function toOpenAI({
14
  command = "CREATE_GAME",
@@ -17,9 +11,16 @@ export async function toOpenAI({
17
  template = "",
18
  model = "gpt-3.5-turbo",
19
  maxTokens = "2048",
 
20
  }) {
21
  const prompt_ = prompt.trim();
22
 
 
 
 
 
 
 
23
  const nextMessage: ChatCompletionRequestMessage = {
24
  role: "user",
25
  content: miniPrompt`
@@ -36,28 +37,13 @@ export async function toOpenAI({
36
  const messages: ChatCompletionRequestMessage[] = [
37
  {
38
  role: "system",
39
- content: miniPrompt`
40
- You are a skilled 2D game developer working with JavaScript on Canvas2D and aim for high performance
41
- You understand and follow a set of "COMMANDS" to build games:
42
-
43
- "${COMMAND_CREATE_GAME}": Initiate the development. Consider the game type, environment, basic mechanics and extend the "TEMPLATE"
44
- "${COMMAND_ADD_FEATURE}": Add the new feature
45
- "${COMMAND_REMOVE_FEATURE}": Remove the existing feature
46
- "${COMMAND_EXTEND_FEATURE}": Modify an existing feature, altering its behavior or properties
47
- "${COMMAND_FIX_BUG}": Debug and fix problems, ensuring everything functions as intended
48
-
49
- NEVER use any external assets: image, base64, sprite or audio
50
- You can use these libraries without importing them: TWEEN, Mousetrap
51
- NEVER use alert! Write your message on Canvas directly
52
- Your "OUTPUT FORMAT" must be valid JavaScript code within a markdown code block
53
- It's crucial to follow these "COMMANDS" and "OUTPUT FORMAT" for the desired results
54
- `,
55
  },
56
  nextMessage,
57
  ];
58
 
59
  try {
60
- const response = await openai.createChatCompletion({
61
  model,
62
  messages,
63
  max_tokens: Number.parseInt(maxTokens),
 
1
  import { ChatCompletionRequestMessage } from "openai";
2
  import { nanoid } from "nanoid";
3
+ import { createClient, openai } from "@/services/api/openai";
4
  import { extractCode, miniPrompt } from "@/utils/prompt";
5
+ import { systemMessage } from "@/constants";
 
 
 
 
 
 
6
 
7
  export async function toOpenAI({
8
  command = "CREATE_GAME",
 
11
  template = "",
12
  model = "gpt-3.5-turbo",
13
  maxTokens = "2048",
14
+ openAIAPIKey = "",
15
  }) {
16
  const prompt_ = prompt.trim();
17
 
18
+ let client = openai;
19
+
20
+ if (openAIAPIKey !== "") {
21
+ client = createClient(openAIAPIKey);
22
+ }
23
+
24
  const nextMessage: ChatCompletionRequestMessage = {
25
  role: "user",
26
  content: miniPrompt`
 
37
  const messages: ChatCompletionRequestMessage[] = [
38
  {
39
  role: "system",
40
+ content: miniPrompt`${systemMessage}`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  },
42
  nextMessage,
43
  ];
44
 
45
  try {
46
+ const response = await client.createChatCompletion({
47
  model,
48
  messages,
49
  max_tokens: Number.parseInt(maxTokens),
src/services/api/openai.ts CHANGED
@@ -5,3 +5,7 @@ export const configuration = new Configuration({
5
  apiKey: process.env.OPENAI_API_KEY,
6
  });
7
  export const openai = new OpenAIApi(configuration);
 
 
 
 
 
5
  apiKey: process.env.OPENAI_API_KEY,
6
  });
7
  export const openai = new OpenAIApi(configuration);
8
+
9
+ export const createClient = (apiKey: string) => {
10
+ return new OpenAIApi(new Configuration({ apiKey }));
11
+ };
src/store/atoms.ts CHANGED
@@ -1,6 +1,6 @@
1
  import { atomWithStorage } from "jotai/utils";
2
 
3
- import { base } from "@/constants";
4
 
5
  export const answersAtom = atomWithStorage<
6
  {
@@ -11,7 +11,7 @@ export const answersAtom = atomWithStorage<
11
  >("2DGameGPT", [
12
  {
13
  id: "1",
14
- content: base.default,
15
  task: "Base Game",
16
  },
17
  ]);
 
1
  import { atomWithStorage } from "jotai/utils";
2
 
3
+ import { baseGame } from "@/constants/baseGame";
4
 
5
  export const answersAtom = atomWithStorage<
6
  {
 
11
  >("2DGameGPT", [
12
  {
13
  id: "1",
14
+ content: baseGame.default,
15
  task: "Base Game",
16
  },
17
  ]);
src/utils/share.tsx CHANGED
@@ -10,20 +10,11 @@ ${renderToString(
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
  )}`;
@@ -36,15 +27,6 @@ ${renderToString(
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
  },
@@ -93,17 +75,36 @@ html, body {
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
  },
 
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" type="module" />
14
  <link rel="stylesheet" href="/style.css" />
15
  </head>
16
  <body>
17
  <canvas id="canvas" />
 
 
 
 
 
 
 
 
 
18
  </body>
19
  </html>
20
  )}`;
 
27
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
28
 
29
  <canvas id="canvas" />
 
 
 
 
 
 
 
 
 
30
  </>
31
  )}`;
32
  },
 
75
  /**
76
  * generated with https://failfa.st
77
  */
78
+
79
+
80
+ /**
81
+ * Global imports
82
+ */
83
+ import Mousetrap from "https://cdn.skypack.dev/[email protected]";
84
+ import confetti from "https://cdn.skypack.dev/[email protected]";
85
+
86
+ /**
87
+ * Helper to handle the resize of the window > canvas automatically
88
+ */
89
  function __2DGameGPT__ResizeHelper(){
90
+ const _canvas = document.querySelector("canvas")
91
+ _canvas.width = window.innerWidth;
92
+ _canvas.height = window.innerHeight;
93
+
94
  function handleResize() {
95
  requestAnimationFrame(() => {
96
+ _canvas.width = window.innerWidth;
97
+ _canvas.height = window.innerHeight;
98
  });
99
  }
100
  handleResize();
101
  window.addEventListener("resize", handleResize, { passive: true });
102
  }
103
  __2DGameGPT__ResizeHelper()
104
+
105
+ /**
106
+ * Generated 2D game
107
+ */
108
  ${content}
109
  `;
110
  },