Skip to content

Commit

Permalink
Merge pull request #17 from sentrychris/feature/050-ability-to-save-g…
Browse files Browse the repository at this point in the history
…ames-load-saved-games

Add ability to save games
  • Loading branch information
sentrychris committed Oct 29, 2023
2 parents c8bcef9 + d913472 commit 488e9e5
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 120 deletions.
16 changes: 13 additions & 3 deletions src/app/IPC.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { ipcMain } = require('electron');

module.exports = class IPC
{
constructor(context, handlers = { settings: null }, { register = false}) {
constructor(context, handlers = { storage: null }, { register = false}) {
this.context = context;
this.contextWindowTitle = 'Undead Bytes';

Expand All @@ -24,8 +24,18 @@ module.exports = class IPC
});

ipcMain.on('to:settings:save', (event, { settings }) => {
// call method belonging to app context Settings handler
this.handlers.settings.saveToFile(settings);
this.handlers.storage.saveSettingsToFile(settings);
});

ipcMain.on('to:game:save', (event, { save }) => {
this.handlers.storage.saveGameToFile(save);
});

ipcMain.on('to:game:load', (event) => {
this.handlers.storage.loadGameFromFile()
.then((save) => {
this.context.webContents.send('from:game:save', save);
});
});
}
}
29 changes: 28 additions & 1 deletion src/app/Menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,43 @@ const openAboutWindow = require('about-window').default;


module.exports = class AppMenu {
constructor(context, { register = false }) {
constructor(context, { register = false, handlers = { storage: null } }) {
this.context = context;
this.handlers = handlers;

if (register) {
this.register();
}
}

attach (handler, instance) {
this.handlers[handler] = instance;
}

register () {
app.applicationMenu = Menu.buildFromTemplate([
{
label: 'Game',
submenu: [
{
label: 'Load Game...',
click: () => {
this.handlers.storage.loadGameFromFile()
.then((save) => {
this.context.webContents.send('from:game:save', save);
});
},
accelerator: 'Ctrl+L'
},
{
label: 'Save Game',
click: () => {
// will need to make a round trip into the web context to get the settings values
},
accelerator: 'Ctrl+S'
}
]
},
{
label: 'View',
submenu: [
Expand Down
63 changes: 0 additions & 63 deletions src/app/Settings.js

This file was deleted.

126 changes: 126 additions & 0 deletions src/app/Storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
const os = require('os');
const fs = require('fs');
const path = require('path');
const { dialog } = require('electron');

module.exports = class Storage
{
constructor (context) {
this.path = path.normalize(os.homedir() + '/.undeadbytesgame/');

if (! fs.existsSync(this.path)) {
fs.mkdirSync(this.path);
}

this.context = context;

this.settingsFile = this.path + 'settings.json';
this.settings = {
volumes: {
fx: {
weapon: 0.5,
snippet: 0.5,
},
soundtrack: 0.5
}
}

this.savedGamesDir = path.normalize(this.path + 'saved_games/');
this.savedGames = [];

if (! fs.existsSync(this.savedGamesDir)) {
fs.mkdirSync(this.savedGamesDir);
}

this.createSettingsIfNotExists();
this.loadSavedGamesFromDir();
}

createSettingsIfNotExists (settings = {}) {
if (! fs.existsSync(this.settingsFile)) {
settings = {
...this.settings,
...settings
};

this.saveSettingsToFile(settings);
}
}

loadSettingsFromFile () {
const settings = JSON.parse(fs.readFileSync(this.settingsFile, {
encoding: 'utf-8'
}));

if (settings) {
this.settings = settings;
}

return this.settings;
}

saveSettingsToFile (settings) {
try {
fs.writeFileSync(this.settingsFile, JSON.stringify(settings, null, 4), {
encoding: 'utf-8'
});
} catch (err) {
console.log(err);
}
}

loadSavedGamesFromDir () {
try {
fs.readdirSync(this.savedGamesDir).forEach((file) => {
this.savedGames.push(file);
});
} catch (err) {
console.log(err)
}
}

async loadGameFromFile () {
return new Promise((resolve) => {
dialog.showOpenDialog({
defaultPath: this.savedGamesDir,
filters: [
{ name: 'Undead Bytes Saves', extensions: ['json'] }
],
properties: ['openFile']
}).then(({ filePaths }) => {
if (filePaths.length === 0) {
throw new Error('noselection');
}

const game = JSON.parse(fs.readFileSync(filePaths[0], {
encoding: 'utf-8'
}));


return resolve(game);
}).catch((err) => {
console.log(err);
})
})
}

saveGameToFile (save) {
try {
console.log('Saving game - ', save);

const date = (new Date()).toISOString()
.slice(0, 19)
.replace('T', '');

const file = `undeadbytes-save-level-${save.level}-${date.replace(/[:-]/g, '')}.json`;
const path = this.savedGamesDir + file;

fs.writeFileSync(path, JSON.stringify(save, null, 4), {
encoding: 'utf-8',
flag: 'a+'
});
} catch (err) {
console.log(err);
}
}
}
6 changes: 3 additions & 3 deletions src/browser/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ canvas {
/***************************|
| Game ended display |
***************************/
.game-ended {
.game-overlay {
display: none;
width: 100%;
height: 100%;
Expand All @@ -220,7 +220,7 @@ canvas {
text-align: center;
}

.game-ended__text {
.game-overlay__text {
display: flex;
flex-direction: column;
align-items: center;
Expand All @@ -231,7 +231,7 @@ canvas {
color: white;
}

.game-ended__text--big {
.game-overlay__text--big {
font-size: 6rem;
text-transform: uppercase;
}
Expand Down
45 changes: 34 additions & 11 deletions src/browser/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Game } from './lib/Game';
import { Settings } from './lib/Settings';
import { Storage } from './lib/Storage';
import { GameDispatcher } from './lib/Events/GameDispatcher';
import {
isActiveElement,
getExecutionBridge,
Expand All @@ -17,22 +18,25 @@ const canvas = document.querySelector('canvas#game');
// Determines whether or not game is running within browser or electron app
const bridge = getExecutionBridge();

// Load game settings
const settings = new Settings(bridge, {
// Create a game dispatcher for dispatching handler events
const dispatcher = new GameDispatcher();

// Load storage (settings, saved games etc.)
const storage = new Storage(bridge, dispatcher, {
register: true
});

// Game setup
function main () {
function main (level = 1) {
if (isActiveElement(viewport) && canvas) {
// Create a new managed game instance
const game = new Game(bridge, canvas.getContext('2d'));
const game = new Game(bridge, dispatcher, canvas.getContext('2d'));

// Attach settings to the game instance
game.attach('settings', settings);
// Attach storage to the game instance
game.attach('storage', storage);

// Setup the level and start the game loop
game.setup({ level: 1 }, true);
game.setup({ level }, true);

// Track WASD for UI
trackWASDKeyboard();
Expand All @@ -42,13 +46,32 @@ function main () {
}
}

// Spash screen play trigger
document.querySelector('#play-now').addEventListener('click', () => {
function play (level = 1) {
if (splash) {
document.body.classList.remove('body-splash');
splash.style.display = 'none';
viewport.classList.remove('inactive');
}

main();
main(level);
}

// Spash screen play trigger
document.querySelector('#play-now').addEventListener('click', () => {
play();
});

// Load game play trigger
document.querySelector('#load-game').addEventListener('click', () => {
if (bridge !== 'web') {
bridge.send('to:game:load');
}
});

// Event listener to instantiate a new game
dispatcher.addEventListener('game:load:instance', (event) => {
const { save } = event;
if (save) {
play(save.level);
}
});
Loading

0 comments on commit 488e9e5

Please sign in to comment.