Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1626989bd7 | |||
| cc22cf4301 | |||
| 37b5511439 | |||
| a29b01f26e |
@@ -0,0 +1,23 @@
|
|||||||
|
/* Main styles for retro 80s aesthetic */
|
||||||
|
:root {
|
||||||
|
--primary-color: #00FF00;
|
||||||
|
--secondary-color: #000080;
|
||||||
|
--background-color: #000000;
|
||||||
|
--text-color: #FFFFFF;
|
||||||
|
--border-color: #00FF00;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/* Loading screen styles */
|
||||||
|
.loading-screen {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-block {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
margin: 0 auto 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-bar {
|
||||||
|
width: 200px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-progress {
|
||||||
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
/* Intro Screen Styles */
|
||||||
|
.intro-screen {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #0a0a1a;
|
||||||
|
color: #00ff00;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-shadow: 0 0 10px #00ff00;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.credits {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: #a0a0a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.instructions {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: #00ff00;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option.selected {
|
||||||
|
border-color: #00ff00;
|
||||||
|
background-color: rgba(0, 255, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option:hover {
|
||||||
|
background-color: rgba(0, 255, 0, 0.2);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>3D Flight Simulator - Loading</title>
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
<link rel="stylesheet" href="css/screens.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loading-screen" class="loading-screen">
|
||||||
|
<div class="loading-content">
|
||||||
|
<div class="logo">
|
||||||
|
<div class="logo-block"></div>
|
||||||
|
<div class="logo-block"></div>
|
||||||
|
<div class="logo-block"></div>
|
||||||
|
<div class="logo-text">WINGS88</div>
|
||||||
|
</div>
|
||||||
|
<div class="copyright">(c) 1988 RetroWeb Games</div>
|
||||||
|
<div class="loading-bar">
|
||||||
|
<div class="loading-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="loading-text">Loading assets...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="js/screens/loading.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<div id="screen" class="screen">
|
||||||
|
<!-- Screen content will be dynamically loaded -->
|
||||||
|
</div>
|
||||||
|
<div id="screen" class="screen">
|
||||||
|
<!-- Screen content will be dynamically loaded -->
|
||||||
|
</div>
|
||||||
|
<div id="screen" class="screen">
|
||||||
|
<!-- Screen content will be dynamically loaded -->
|
||||||
|
</div>
|
||||||
|
<div id="screen" class="screen">
|
||||||
|
<!-- Screen content will be dynamically loaded -->
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
class IntroScreen {
|
||||||
|
constructor() {
|
||||||
|
this.title = 'WINGS88';
|
||||||
|
this.credits = 'A RetroWeb Games Production';
|
||||||
|
this.instructions = 'Use arrow keys to fly\nPress SPACE to start';
|
||||||
|
this.options = ['Start Game', 'Instructions', 'Quit'];
|
||||||
|
this.selectedOption = 0;
|
||||||
|
this.audioContext = null;
|
||||||
|
this.musicTrack = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.setupAudio();
|
||||||
|
this.render();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAudio() {
|
||||||
|
try {
|
||||||
|
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
this.loadMusic();
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Audio initialization failed:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMusic() {
|
||||||
|
// Placeholder for retro MIDI music
|
||||||
|
console.log('Loading retro music...');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const screen = document.getElementById('screen');
|
||||||
|
screen.innerHTML = `
|
||||||
|
<div class="intro-screen">
|
||||||
|
<div class="title">${this.title}</div>
|
||||||
|
<div class="credits">${this.credits}</div>
|
||||||
|
<div class="instructions">${this.instructions}</div>
|
||||||
|
<div class="options">
|
||||||
|
${this.options.map((option, index) => `
|
||||||
|
<div class="option ${index === this.selectedOption ? 'selected' : ''}">
|
||||||
|
${option}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
document.addEventListener('keydown', (e) => this.handleKeyDown(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(e) {
|
||||||
|
switch(e.key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
this.moveSelection(-1);
|
||||||
|
break;
|
||||||
|
case 'ArrowDown':
|
||||||
|
this.moveSelection(1);
|
||||||
|
break;
|
||||||
|
case 'Enter':
|
||||||
|
this.selectOption();
|
||||||
|
break;
|
||||||
|
case 'Escape':
|
||||||
|
this.selectOption();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveSelection(direction) {
|
||||||
|
this.selectedOption = (this.selectedOption + direction + this.options.length) % this.options.length;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOption() {
|
||||||
|
switch(this.selectedOption) {
|
||||||
|
case 0:
|
||||||
|
console.log('Starting game...');
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
console.log('Showing instructions...');
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
console.log('Quitting game...');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
if (this.audioContext) {
|
||||||
|
this.audioContext.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = IntroScreen;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Loading screen functionality for WINGS88 3D Flight Simulator
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LoadingScreen {
|
||||||
|
constructor() {
|
||||||
|
this.progress = 0;
|
||||||
|
this.assets = [
|
||||||
|
'texture1.png',
|
||||||
|
'texture2.png',
|
||||||
|
'sound1.mp3',
|
||||||
|
'sound2.mp3',
|
||||||
|
'model1.obj',
|
||||||
|
'model2.obj'
|
||||||
|
];
|
||||||
|
this.audioContext = null;
|
||||||
|
this.audioBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.setupEventListeners();
|
||||||
|
this.startLoading();
|
||||||
|
this.initAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startLoading() {
|
||||||
|
const progressBar = document.querySelector('.loading-progress');
|
||||||
|
const loadingText = document.querySelector('.loading-text');
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
this.progress += 5;
|
||||||
|
if (progressBar) progressBar.style.width = `${this.progress}%`;
|
||||||
|
|
||||||
|
if (this.progress >= 100) {
|
||||||
|
clearInterval(interval);
|
||||||
|
if (loadingText) loadingText.textContent = 'Loading complete! Starting game...';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.transitionToIntro();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
initAudio() {
|
||||||
|
try {
|
||||||
|
// Check if audio is supported
|
||||||
|
if (typeof AudioContext !== 'undefined') {
|
||||||
|
this.audioContext = new AudioContext();
|
||||||
|
this.loadBackgroundMusic();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Audio initialization error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBackgroundMusic() {
|
||||||
|
// In a real implementation, this would load actual audio files
|
||||||
|
// For now, we'll simulate loading
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Background music loaded');
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
playBackgroundMusic() {
|
||||||
|
if (this.audioContext && this.audioBuffer) {
|
||||||
|
const source = this.audioContext.createBufferSource();
|
||||||
|
source.buffer = this.audioBuffer;
|
||||||
|
source.connect(this.audioContext.destination);
|
||||||
|
source.start(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitionToIntro() {
|
||||||
|
// In a real implementation, this would transition to the intro screen
|
||||||
|
console.log('Transitioning to intro screen');
|
||||||
|
// For now, we'll just log the transition
|
||||||
|
document.querySelector('.loading-text').textContent = 'Welcome to WINGS88!';
|
||||||
|
}
|
||||||
|
|
||||||
|
getProgress() {
|
||||||
|
return this.progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize loading screen when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const loadingScreen = new LoadingScreen();
|
||||||
|
loadingScreen.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export for testing
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = LoadingScreen;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
const IntroScreen = require('../../src/js/screens/intro');
|
||||||
|
|
||||||
|
describe('IntroScreen', () => {
|
||||||
|
let introScreen;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
introScreen = new IntroScreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should initialize with correct default values', () => {
|
||||||
|
expect(introScreen.title).toBe('WINGS88');
|
||||||
|
expect(introScreen.credits).toBe('A RetroWeb Games Production');
|
||||||
|
expect(introScreen.instructions).toBe('Use arrow keys to fly\nPress SPACE to start');
|
||||||
|
expect(introScreen.options).toEqual(['Start Game', 'Instructions', 'Quit']);
|
||||||
|
expect(introScreen.selectedOption).toBe(0);
|
||||||
|
expect(introScreen.audioContext).toBeNull();
|
||||||
|
expect(introScreen.musicTrack).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('moveSelection', () => {
|
||||||
|
it('should move selection up', () => {
|
||||||
|
introScreen.selectedOption = 1;
|
||||||
|
// Mock render to prevent DOM access
|
||||||
|
introScreen.render = jest.fn();
|
||||||
|
introScreen.moveSelection(-1);
|
||||||
|
expect(introScreen.selectedOption).toBe(0);
|
||||||
|
expect(introScreen.render).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move selection down', () => {
|
||||||
|
introScreen.selectedOption = 0;
|
||||||
|
// Mock render to prevent DOM access
|
||||||
|
introScreen.render = jest.fn();
|
||||||
|
introScreen.moveSelection(1);
|
||||||
|
expect(introScreen.selectedOption).toBe(1);
|
||||||
|
expect(introScreen.render).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap around from last to first', () => {
|
||||||
|
introScreen.selectedOption = 2;
|
||||||
|
// Mock render to prevent DOM access
|
||||||
|
introScreen.render = jest.fn();
|
||||||
|
introScreen.moveSelection(1);
|
||||||
|
expect(introScreen.selectedOption).toBe(0);
|
||||||
|
expect(introScreen.render).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wrap around from first to last', () => {
|
||||||
|
introScreen.selectedOption = 0;
|
||||||
|
// Mock render to prevent DOM access
|
||||||
|
introScreen.render = jest.fn();
|
||||||
|
introScreen.moveSelection(-1);
|
||||||
|
expect(introScreen.selectedOption).toBe(2);
|
||||||
|
expect(introScreen.render).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('selectOption', () => {
|
||||||
|
it('should handle Start Game selection', () => {
|
||||||
|
introScreen.selectedOption = 0;
|
||||||
|
const logSpy = jest.spyOn(console, 'log');
|
||||||
|
introScreen.selectOption();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Starting game...');
|
||||||
|
logSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Instructions selection', () => {
|
||||||
|
introScreen.selectedOption = 1;
|
||||||
|
const logSpy = jest.spyOn(console, 'log');
|
||||||
|
introScreen.selectOption();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Showing instructions...');
|
||||||
|
logSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Quit selection', () => {
|
||||||
|
introScreen.selectedOption = 2;
|
||||||
|
const logSpy = jest.spyOn(console, 'log');
|
||||||
|
introScreen.selectOption();
|
||||||
|
expect(logSpy).toHaveBeenCalledWith('Quitting game...');
|
||||||
|
logSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Unit tests for LoadingScreen class
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LoadingScreen = require('../../src/js/screens/loading');
|
||||||
|
|
||||||
|
describe('LoadingScreen', () => {
|
||||||
|
let loadingScreen;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
loadingScreen = new LoadingScreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('constructor', () => {
|
||||||
|
it('should initialize with progress 0', () => {
|
||||||
|
expect(loadingScreen.getProgress()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have assets array', () => {
|
||||||
|
expect(loadingScreen.assets).toBeDefined();
|
||||||
|
expect(Array.isArray(loadingScreen.assets)).toBe(true);
|
||||||
|
expect(loadingScreen.assets.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getProgress', () => {
|
||||||
|
it('should return current progress', () => {
|
||||||
|
expect(loadingScreen.getProgress()).toBe(0);
|
||||||
|
|
||||||
|
// Simulate progress update
|
||||||
|
loadingScreen.progress = 50;
|
||||||
|
expect(loadingScreen.getProgress()).toBe(50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('asset loading', () => {
|
||||||
|
it('should have expected assets', () => {
|
||||||
|
expect(loadingScreen.assets).toContain('texture1.png');
|
||||||
|
expect(loadingScreen.assets).toContain('texture2.png');
|
||||||
|
expect(loadingScreen.assets).toContain('sound1.mp3');
|
||||||
|
expect(loadingScreen.assets).toContain('sound2.mp3');
|
||||||
|
expect(loadingScreen.assets).toContain('model1.obj');
|
||||||
|
expect(loadingScreen.assets).toContain('model2.obj');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user