diff --git a/package-lock.json b/package-lock.json
index 77595d1..a8a2b67 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,13 @@
{
"name": "vcs-game-maker",
- "version": "0.4.0",
+ "version": "0.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "version": "0.4.0",
+ "version": "0.5.0",
"dependencies": {
+ "@curtishughes/pixel-editor": "^4.0.1",
"@vue/composition-api": "^1.0.0-rc.13",
"batari-basic": "^0.0.1",
"blockly": "^6.20210701.0",
@@ -1070,6 +1071,11 @@
"to-fast-properties": "^2.0.0"
}
},
+ "node_modules/@curtishughes/pixel-editor": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@curtishughes/pixel-editor/-/pixel-editor-4.0.1.tgz",
+ "integrity": "sha512-y9Ntl+Z/FonWEFxdEAVXH1D+iU9QorH4AckM4ZuYfyWiKoxsLZf6E+dJwwuIAi6gEyKueFcYgDjlU1QAjJu9sA=="
+ },
"node_modules/@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
@@ -16010,6 +16016,11 @@
"to-fast-properties": "^2.0.0"
}
},
+ "@curtishughes/pixel-editor": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@curtishughes/pixel-editor/-/pixel-editor-4.0.1.tgz",
+ "integrity": "sha512-y9Ntl+Z/FonWEFxdEAVXH1D+iU9QorH4AckM4ZuYfyWiKoxsLZf6E+dJwwuIAi6gEyKueFcYgDjlU1QAjJu9sA=="
+ },
"@eslint/eslintrc": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz",
diff --git a/package.json b/package.json
index 22a62c4..3d654d1 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "vcs-game-maker",
- "version": "0.5.0",
+ "version": "0.6.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
@@ -8,6 +8,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
+ "@curtishughes/pixel-editor": "^4.0.1",
"@vue/composition-api": "^1.0.0-rc.13",
"batari-basic": "^0.0.1",
"blockly": "^6.20210701.0",
diff --git a/src/components/PixelEditor.vue b/src/components/PixelEditor.vue
new file mode 100644
index 0000000..2b8c9c2
--- /dev/null
+++ b/src/components/PixelEditor.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+ mdi-eraser
+
+
+ mdi-pencil
+
+
+
+ editor.undo()"
+ >
+ mdi-undo
+
+ editor.redo()"
+ >
+ mdi-redo
+
+
+
+
+
+
diff --git a/src/generators/bbasic.bb.hbs b/src/generators/bbasic.bb.hbs
index b3ec8f1..caa5cd7 100644
--- a/src/generators/bbasic.bb.hbs
+++ b/src/generators/bbasic.bb.hbs
@@ -5,17 +5,7 @@
rem - shape of the playfield and player sprites
rem **************************************************************************
playfield:
- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- X....X...................X....X
- X.............................X
- X.............................X
- X.............................X
- X.............................X
- X.............................X
- X.............................X
- X.............................X
- X....X...................X....X
- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+{{ playField }}
end
player0:
diff --git a/src/generators/bbasic.js b/src/generators/bbasic.js
index 3f0050e..aae0d14 100644
--- a/src/generators/bbasic.js
+++ b/src/generators/bbasic.js
@@ -14,6 +14,9 @@ import Blockly from 'blockly/core';
import templateText from 'raw-loader!./bbasic.bb.hbs';
import Handlebars from 'handlebars';
+import {useBackgroundsStorage} from '../hooks/project';
+import {matrixToPlayfield} from '../utils/pixels';
+
const handlebarsTemplate = Handlebars.compile(templateText);
/**
@@ -172,11 +175,13 @@ Blockly.BBasic.finish = function(code) {
code = Object.getPrototypeOf(this).finish.call(this, code);
code = code.replace(/^[\t ]*/gm, Blockly.BBasic.INDENT);
+ const playField = Blockly.BBasic.generateBackgrounds();
+
this.isInitialized = false;
this.nameDB_.reset();
const generatedBody = definitions.join('\n\n') + '\n\n\n' + code;
- return handlebarsTemplate({generatedBody});
+ return handlebarsTemplate({generatedBody, playField});
};
/**
@@ -322,6 +327,37 @@ Blockly.BBasic.getAdjusted = function(block, atId, optDelta, optNegate,
return at;
};
+Blockly.BBasic.generateBackgrounds = function() {
+ const backgroundsStorage = useBackgroundsStorage();
+
+ let backgroundData = null;
+ try {
+ backgroundData = backgroundsStorage.value;
+ } catch (e) {
+ console.error('Failed to load backgrounds', e);
+ }
+
+ const backgrounds = backgroundData && backgroundData.backgrounds;
+ let playField =
+ 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n' +
+ 'X....X...................X....X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X.............................X\n' +
+ 'X....X...................X....X\n' +
+ 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
+
+ if (backgrounds && backgrounds[0] && backgrounds[0].pixels) {
+ playField = matrixToPlayfield(backgrounds[0].pixels);
+ }
+
+ return playField.split('\n').map((line) => ' ' + line).join('\n');
+};
+
import collision from './bbasic/collision';
import colour from './bbasic/colour';
import input from './bbasic/input';
diff --git a/src/hooks/project.js b/src/hooks/project.js
index 17ca53e..a65d765 100644
--- a/src/hooks/project.js
+++ b/src/hooks/project.js
@@ -1,4 +1,10 @@
-import {useLocalStorage} from '../hooks/storage';
+import {useJsonLocalStorage, useLocalStorage} from '../hooks/storage';
+
+const keyOf = (type) =>`vcs-game-maker.${type}`;
+
+export const useProjectStorage = (type) => useLocalStorage(keyOf(type));
+export const useJsonProjectStorage = (type) => useJsonLocalStorage(keyOf(type));
-export const useProjectStorage = (type) => useLocalStorage(`vcs-game-maker.${type}`);
export const useWorkspaceStorage = () => useProjectStorage('workspace');
+export const useBackgroundsStorage = () => useJsonProjectStorage('backgrounds');
+
diff --git a/src/hooks/storage.js b/src/hooks/storage.js
index 4e8864b..d828d42 100644
--- a/src/hooks/storage.js
+++ b/src/hooks/storage.js
@@ -11,3 +11,13 @@ export const useLocalStorage = (key) => computed({
localStorage.setItem(key, value);
},
});
+
+export const useJsonLocalStorage = (key) => computed({
+ get() {
+ const jsonText = localStorage.getItem(key);
+ return jsonText ? JSON.parse(jsonText) : null;
+ },
+ set(value) {
+ localStorage.setItem(key, JSON.stringify(value));
+ },
+});
diff --git a/src/router/index.js b/src/router/index.js
index 1b9ea4c..d3de110 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -19,6 +19,11 @@ const routes = [
component: () => import(/* webpackChunkName: "about" */
'../views/About.vue'),
},
+ {
+ path: '/background',
+ name: 'Background',
+ component: () => import('../views/BackgroundEditor.vue'),
+ },
{
path: '/generated',
name: 'Generated',
diff --git a/src/utils/array.js b/src/utils/array.js
new file mode 100644
index 0000000..d5f8bfe
--- /dev/null
+++ b/src/utils/array.js
@@ -0,0 +1,25 @@
+// Based on https://stackoverflow.com/a/16436975/679240
+export const isArrayEqual = (a, b) => {
+ if (a === b) return true;
+ if (a == null || b == null) return false;
+ if (a.length !== b.length) return false;
+
+ for (let i = 0; i < a.length; ++i) {
+ if (a[i] !== b[i]) return false;
+ }
+ return true;
+};
+
+export const isMatrixEqual = (a, b) => {
+ if (a === b) return true;
+ if (a == null || b == null) return false;
+ if (a.length !== b.length) return false;
+
+ for (let i = 0; i < a.length; ++i) {
+ if (!isArrayEqual(a[i], b[i])) return false;
+ }
+
+ return true;
+};
+
+export const copyMatrix = (original) => original.map((row) => row.slice());
diff --git a/src/utils/pixels.js b/src/utils/pixels.js
new file mode 100644
index 0000000..91c52b6
--- /dev/null
+++ b/src/utils/pixels.js
@@ -0,0 +1,6 @@
+export const playfieldToMatrix = (text) => text.trim().split('\n')
+ .map((line) => line.trim().split('').map((ch) => ch === 'X' ? 1 : 0));
+
+export const matrixToPlayfield = (matrix) => matrix
+ .map((line) => line.map((pixel) => pixel ? 'X' : '.').join(''))
+ .join('\n').trim();
diff --git a/src/views/BackgroundEditor.vue b/src/views/BackgroundEditor.vue
new file mode 100644
index 0000000..c0ccc73
--- /dev/null
+++ b/src/views/BackgroundEditor.vue
@@ -0,0 +1,87 @@
+
+
+ Backgrounds
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/Project.vue b/src/views/Project.vue
index e129bfc..209f511 100644
--- a/src/views/Project.vue
+++ b/src/views/Project.vue
@@ -24,7 +24,8 @@ import {defineComponent, reactive} from '@vue/composition-api';
import {saveAs} from 'file-saver';
import YAML from 'yaml';
-import {useWorkspaceStorage} from '../hooks/project';
+import {useBackgroundsStorage, useWorkspaceStorage} from '../hooks/project';
+import {matrixToPlayfield, playfieldToMatrix} from '../utils/pixels';
const FORMAT_TYPE = 'VCS Game Maker Project';
const FORMAT_VERSION = 1.0;
@@ -33,16 +34,27 @@ export default defineComponent({
setup(props, context) {
const data = reactive({fileToImport: null});
const router = context.root.$router;
+
+ const backgroundsStorage = useBackgroundsStorage();
const workspaceStorage = useWorkspaceStorage();
- return {data, router, workspaceStorage};
+
+ return {data, router, backgroundsStorage, workspaceStorage};
},
methods: {
handleSaveProject() {
+ const backgrounds = !this.backgroundsStorage ? null :
+ {
+ ...this.backgroundsStorage,
+ backgrounds: this.backgroundsStorage.backgrounds
+ .map((bkg) => ({...bkg, pixels: matrixToPlayfield(bkg.pixels)})),
+ };
+
const projectYaml = YAML.stringify({
'type': FORMAT_TYPE,
'format-version': FORMAT_VERSION,
'generation-time': new Date(),
'blockly-workspace': this.workspaceStorage,
+ backgrounds,
});
const projectBlob = new Blob([projectYaml], {type: 'text/yaml'});
@@ -55,7 +67,6 @@ export default defineComponent({
return;
}
- console.info('Importing file', this.data.fileToImport);
const reader = new FileReader();
reader.readAsText(this.data.fileToImport, 'UTF-8');
reader.onload = (evt) => {
@@ -74,6 +85,15 @@ export default defineComponent({
this.workspaceStorage = project['blockly-workspace'];
+ if (project.backgrounds) {
+ const backgrounds = {
+ ...project.backgrounds,
+ backgrounds: project.backgrounds.backgrounds
+ .map((bkg) => ({...bkg, pixels: playfieldToMatrix(bkg.pixels)})),
+ };
+ this.backgroundsStorage = backgrounds;
+ }
+
this.router.push('/');
};
reader.onerror = (evt) => console.error('Error while loading project', evt);