feat: Implement intro screen with retro 80s aesthetic and unit tests #3
@@ -60,3 +60,58 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--text-color);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,3 +27,15 @@
|
|||||||
<script src="js/screens/loading.js"></script>
|
<script src="js/screens/loading.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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,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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user