Skip to content

Instantly share code, notes, and snippets.

@patiboh
Last active September 13, 2022 15:58
Show Gist options
  • Select an option

  • Save patiboh/dc4825eefe516a567538864236d47df5 to your computer and use it in GitHub Desktop.

Select an option

Save patiboh/dc4825eefe516a567538864236d47df5 to your computer and use it in GitHub Desktop.
Emoji Space Invaders
/**
* INVADER MOVES
*/
@keyframes horizontal {
from {
margin-inline-start: 0;
}
6.25% {
margin-inline-start: 3%;
}
12.5% {
margin-inline-start: 6%;
}
18.75% {
margin-inline-start: 9%;
}
25% {
margin-inline-start: 12%;
}
31.25% {
margin-inline-start: 15%;
}
37.5% {
margin-inline-start: 18%;
}
43.75% {
margin-inline-start: 21%;
}
50% {
margin-inline-start: 24%;
}
56.25% {
margin-inline-start: 21%;
}
62.5% {
margin-inline-start: 18%;
}
68.75% {
margin-inline-start: 15%;
}
75% {
margin-inline-start: 12%;
}
81.25% {
margin-inline-start: 9%;
}
87.5% {
margin-inline-start: 6%;
}
93.75% {
margin-inline-start: 3%;
}
100% {
margin-inline-start: 0;
}
}
.invaderRow.horizontal .invader:first-child {
animation-duration: 10s;
animation-name: horizontal;
animation-iteration-count: infinite;
}
.invaderRow.vertical:first-child {
transition-duration: 2s;
}
.invaderRow.vertical-1 {
margin-block-start: 6%;
}
.invaderRow.vertical-2 {
margin-block-start: 12%;
}
.invaderRow.vertical-3 {
margin-block-start: 18%;
}
.invaderRow.vertical-4 {
margin-block-start: 24%;
}
.invaderRow.vertical-5 {
margin-block-start: 30%;
}
.invaderRow.vertical-6 {
margin-block-start: 36%;
}
.invaderRow.vertical-7 {
margin-block-start: 42%;
}
.invaderRow.vertical-8 {
margin-block-start: 48%;
}
.invaderRow.vertical-9 {
margin-block-start: 54%;
}
.invaderRow.vertical-10 {
margin-block-start: 60%;
}
/**
* PLAYER MOVES
*/
.player.horizontal-0 {
margin-inline-start: 3%;
}
.player.horizontal-1 {
margin-inline-start: 6%;
}
.player.horizontal-2 {
margin-inline-start: 9%;
}
.player.horizontal-3 {
margin-inline-start: 12%;
}
.player.horizontal-4 {
margin-inline-start: 15%;
}
.player.horizontal-5 {
margin-inline-start: 18%;
}
.player.horizontal-6 {
margin-inline-start: 21%;
}
.player.horizontal-7 {
margin-inline-start: 24%;
}
.player.horizontal-8 {
margin-inline-start: 27%;
}
.player.horizontal-9 {
margin-inline-start: 30%;
}
.player.horizontal-10 {
margin-inline-start: 33%;
}
.player.horizontal-11 {
margin-inline-start: 36%;
}
.player.horizontal-12 {
margin-inline-start: 39%;
}
.player.horizontal-13 {
margin-inline-start: 42%;
}
.player.horizontal-14 {
margin-inline-start: 45%;
}
.player.horizontal-15 {
margin-inline-start: 48%;
}
.player.horizontal-16 {
margin-inline-start: 51%;
}
.player.horizontal-17 {
margin-inline-start: 54%;
}
.player.horizontal-18 {
margin-inline-start: 57%;
}
.player.horizontal-19 {
margin-inline-start: 60%;
}
.player.horizontal-20 {
margin-inline-start: 63%;
}
.player.horizontal-21 {
margin-inline-start: 66%;
}
.player.horizontal-22 {
margin-inline-start: 69%;
}
.player.horizontal-23 {
margin-inline-start: 72%;
}
.player.horizontal-24 {
margin-inline-start: 75%;
}
.player.horizontal-25 {
margin-inline-start: 78%;
}
.player.horizontal-26 {
margin-inline-start: 81%;
}
.player.horizontal-27 {
margin-inline-start: 84%;
}
.player.horizontal-28 {
margin-inline-start: 87%;
}
.player.horizontal-29 {
margin-inline-start: 90%;
}
.player.horizontal-30 {
margin-inline-start: 93%;
}
.player.horizontal-31 {
margin-inline-start: 95%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" href="./animations.css" />
<title>👾 | Space Invaders</title>
</head>
<body>
<header>
<h1>👾 Invaders 🐙</h1>
<button id="play-button" type="button">
<span class="play">👾 Play</span><span class="stop">🐙 Stop</span>
</button>
</header>
<main id="game" class="game">
<section id="invaders" class="invaders"></section>
<section id="trenches" class="trenches"></section>
<section id="ground" class="ground"></section>
<section id="underground" class="underground"></section>
</main>
<script type="module" src="./space-invaders.js"></script>
</body>
</html>
/**
* HTML Elements
*/
const game = document.getElementById('game')
const invadersZone = document.getElementById('invaders')
const trenchesZone = document.getElementById('trenches')
const playerZone = document.getElementById('ground')
const scoreZone = document.getElementById('underground')
const playButton = document.getElementById('play-button')
let invaderRows
let player
/**
* GAME Variables
*/
let invadersLevel = 0
let maxInvadersLevel = 6
let playerPosition = 0
let maxPlayerPosition = 31
let gameSpeed = 10000
let gameOn = false
let gameLoop
/**
* GAME UI Setup
*/
const invaderSeeds = ['👾', '🐲', '👻', '👽', '🐙']
const trenchSeeds = ['🪨']
const playerSeeds = ['🐳', '🐳', '🐳']
function makeInvaders(seeds, zone, number) {
for (let row = 0; row < seeds.length; row++) {
let div = document.createElement('div')
div.classList.add('invaderRow')
for (let i = 0; i < number; i++) {
let s = document.createElement('span')
s.innerHTML = seeds[row]
s.classList.add('invader')
div.append(s)
}
zone.append(div)
}
}
function makeTrenches(seeds, zone, number) {
for (let row = 0; row < seeds.length; row++) {
let div = document.createElement('div')
div.classList.add('trenchRow')
for (let i = 0; i < number; i++) {
let s = document.createElement('span')
s.innerHTML = seeds[row]
s.classList.add('trench')
div.append(s)
}
zone.append(div)
}
}
function makePlayer(seeds, playerZone, scoreZone) {
let div = document.createElement('div')
div.classList.add('playerRow')
player = document.createElement('span')
// player.setAttribute('role', 'input')
player.innerHTML = seeds[0]
player.classList.add('player')
div.append(player)
playerZone.append(div)
const remainingSeeds = seeds.splice(1)
div = document.createElement('div')
div.classList.add('scoreRow')
for (let i = 0; i < remainingSeeds.length; i++) {
let life = document.createElement('span')
life.innerHTML = remainingSeeds[i]
life.classList.add('lives')
div.append(life)
}
scoreZone.append(div)
}
function invadersDown() {
if (invadersLevel === maxInvadersLevel) {
if (gameSpeed >= 200) {
gameSpeed = gameSpeed - 100
}
Array.from(invaderRows).forEach((row) => {
row.classList.remove('horizontal')
row.classList.remove('vertical')
row.classList.remove(`vertical-${invadersLevel}`)
})
playButton.classList.remove('playing')
invadersLevel = 0
playerPosition = 0
play()
} else {
Array.from(invaderRows)[0].classList.remove(`vertical-${invadersLevel}`)
Array.from(invaderRows)[0].classList.add(`vertical-${++invadersLevel}`)
}
}
function play() {
if (invaderRows === undefined) {
invaderRows = document.querySelectorAll('.invaderRow')
}
Array.from(invaderRows).forEach((row) => {
row.classList.add('horizontal')
row.classList.add('vertical')
row.classList.add(`vertical-${invadersLevel}`)
})
playButton.classList.add('playing')
if (!gameLoop) {
gameLoop = setInterval(invadersDown, gameSpeed)
}
}
function stop() {
Array.from(invaderRows).forEach((row) => {
row.classList.remove('horizontal')
row.classList.remove('vertical')
row.classList.remove(`vertical-${invadersLevel}`)
})
playButton.classList.remove('playing')
invadersLevel = 0
playerPosition = 0
clearInterval(gameLoop)
}
function setUp() {
makeInvaders(invaderSeeds, invadersZone, 11)
makeTrenches(trenchSeeds, trenchesZone, 4)
makePlayer(playerSeeds, playerZone, scoreZone)
playButton.addEventListener('click', (e) => {
if (!gameOn) {
play()
} else {
stop()
}
gameOn = !gameOn
})
document.addEventListener(
'keydown',
(event) => {
switch (event.key) {
case 'ArrowRight':
if (playerPosition === maxPlayerPosition) {
return
} else {
player.classList.remove(`horizontal-${playerPosition}`)
player.classList.add(`horizontal-${++playerPosition}`)
}
break
case 'ArrowLeft':
if (playerPosition === -1) {
return
} else {
player.classList.remove(`horizontal-${playerPosition}`)
player.classList.add(`horizontal-${--playerPosition}`)
}
break
}
},
false,
)
}
setUp()

Space Invaders

Running the program

To run the program you can run server locally using python. Once you have python installed, run the following command at the project root:

python -m http.server --bind 127.0.0.1

Then navigate to http://127.0.0.1:8000/ to launch the game

  • indedx.html loads the page
  • spade-invaders.js contains the game logic
  • styles.css contains the game layout and UI styles
  • animations.css contains the CSS animation styles used in the game

Game Description

DONE

The invaders move, and the player can be controlled using the arrow keys

It's missing:

  • the shooting
  • scores
  • a clear GameOver state

It would be nice to have:

  • player lifecycle
  • levels
  • scores
  • player instructions
html {
display: flex;
flex-direction: column;
font-family: sans-serif;
height: 100%;
padding: 0;
margin: 0;
background-color: lightcyan;
}
body {
display: flex;
flex-direction: column;
padding: 1rem;
height: 100%;
}
/**
* GAME HEADER
*/
header {
padding: 0;
margin-bottom: 1rem;
text-align: center;
}
h1 {
text-align: center;
text-transform: uppercase;
font-size: 2rem;
margin: 0 0 0.5rem;
}
button {
background-color: turquoise;
border: 3px solid turquoise;
color: purple;
font-size: 1rem;
font-weight: 600;
text-transform: uppercase;
padding: 0.5em 1em;
margin: 0 0.5em;
}
button:hover {
background-color: yellow;
border-color: yellow;
}
button:focus {
outline-color: fuchsia;
}
button.playing {
border-color: fuchsia;
color: fuchsia;
}
button .stop {
display: none;
}
button.playing .play {
display: none;
}
button.playing .stop {
display: inline;
}
/**
* GAME PLAYGROUND
*/
.game {
background-color: lightgoldenrodyellow;
padding: 2rem;
display: flex;
flex-direction: column;
max-height: calc(100vh - 8rem);
margin: auto;
aspect-ratio: 1/1;
}
.invaders {
flex-basis: 70%;
width: 100%;
height: 100%;
}
.invader {
font-size: calc((100vh - 2rem) / 28);
}
.invader + *,
.lives + * {
margin-inline-start: 0.5rem;
}
.player,
.lives {
font-size: calc((100vh - 2rem) / 28);
}
.trench {
font-size: calc((100vh - 2em) / 12);
width: auto;
margin: 0 2rem;
}
.invaderRow,
.trenchRow,
.playerRow,
.scoreRow {
width: 100%;
display: flex;
}
.invaderRow {
justify-content: flex-start;
}
.trenchRow {
justify-content: center;
}
.scoreRow {
border-top: 3px solid purple;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment