I am trying to build a simple sound machine with various sounds only using JavaScript, specifically, Zelda Ocarina of Time sounds. Yes, I know it's basic stuff, but I am still learning and can't for the life of me figure this out.
Now, I can get the left and right arrow keys to cycle through the choices, but when I use the keys: A,S,D,F,G, or H I can't get them to make a selection. No matter how many avenues I have tried.
Any help, tips, pointers, or ANYTHING constructive would be appreciated.
Sound Machine in browser
CODE:
const regions = Array.from(document.querySelectorAll('section[role="region"]'));
const keys = Array.from(document.querySelectorAll('#keys button'))
const keyTypes = keys.map(op => op.innerHTML);
const moveFocus = (region, item) => {
region[item].focus();
}
const handleArrowEvent = (event, items, currentRegion) => {
let currentItem =0;
if(
event.code === 'ArrowLeft' ||
event.code === 'ArrowRight' ||
event.code === 'KeyA' ||
event.code === 'KeyS' ||
event.code === 'KeyD' ||
event.code === 'KeyF' ||
event.code === 'KeyG' ||
event.code === 'KeyH'
) {
event.preventDefault();
event.stopPropagation();
console.log(event.target);
const regionItems = Array.from(currentRegion.children);
regionItems.forEach(child => {
items.push(child)
})
currentItem = items.indexOf(event.target);
const lastItem = items.length - 1;
const isFirst = currentItem === 0;
const isLast = currentItem === lastItem;
if(event.code === 'ArrowRight') {
currentItem = isLast ? 0 : currentItem + 1;
} else if (event.code === 'ArrowLeft') {
currentItem = isFirst ? lastItem : currentItem - 1;
}
moveFocus(regionItems, currentItem)
}
}
const handleClick = event => {
registerInput(event.target.innerHTML)
}
const handleKeyEvent = event => {
const items = [];
const currentRegion = event.target.closest('section[role="region"]')
if (
event.code === 'ArrowLeft' ||
event.code === 'ArrowRight' ||
event.code === 'KeyA' ||
event.code === 'KeyS' ||
event.code === 'KeyD' ||
event.code === 'KeyF' ||
event.code === 'KeyG' ||
event.code === 'KeyH'
) { handleArrowEvent(event, items, currentRegion) }
}
const registerInput = input => {
console.log(input)
}
#import url('https://fonts.googleapis.com/css2?family=Bangers&family=Heebo:wght#300&family=Press+Start+2P&display=swap');
html {
font-size: 10px;
background-image: url(../assets/img/lofz_ocTime.jpg);
background-size: cover;
}
body , html {
margin: 0;
padding: 0;
font-family: 'Bangers', cursive;
font-size: 12px;
}
#keys {
display: flex;
flex: 1;
min-height: 80vh;
align-items: center;
justify-content: center;
}
button {
border: .4rem solid rgb(255, 5, 5);
border-radius: .5rem;
margin: 1rem;
font-size: 1.5rem;
padding: 1rem .5rem;
transition: all 0.07s ease;
width: 10rem;
text-align: center;
color: rgb(31, 240, 3);
background: rgba(8, 7, 94, 0.753);
font-weight: bold;
text-shadow: 0 0 .5rem black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./css/stylesheet.css">
<link rel="stylesheet" href="./css/normalize.css">
<title>Zelda: Ocarina of Time Sounds</title>
</head>
<body>
<section id="keys" aria-label="Zelda Sounds" role="region">
<button data-key="65" class="sound" class="key" aria-label="Key A">A<aside>Hey! Over Here!</aside></button>
<button data-key="83" class="sound" class="key" aria-label="Key S">S<aside>Link, Listen!</aside></button>
<button data-key="68" class="sound" class="key" aria-label="Key D">D<aside>Gold Skulltula</aside></button>
<button data-key="70" class="sound" class="key" aria-label="Key F">F<aside>Item Found!</aside></button>
<button data-key="71" class="sound" class="key" aria-label="Key G">G<aside>Silver Rupee</aside></button>
<button data-key="72" class="sound" class="key" aria-label="Key H">H<aside>Found a Heart!</aside></button>
</section>
<script src="js/main.js" type="module" defer></script>
</body>
</html>
you should try listening to keyboard input using 'keydown' event
document.addEventListener('keydown', function(e) {
if(e.code == "KeyA") {
console.log('A was pressed');
}
});
You can find the code for the key you want to listen using this website with the event.code value :
https://www.toptal.com/developers/keycode
Hope it helps !
Related
I'm attempting a Rock, Paper, Scissors game using Javascript. I'm new to Javascript, so I don't know much. Each time I click a button I can get the values both for playerSelection and computerSelection but when I try to run the function playRound() it seems like it can't "reach" the values returned by clicking the buttons. What am I doing wrong?
const selectionButtons = document.querySelectorAll('[data-selection]')
const options = ['rock', 'paper', 'scissors']
function computerPlay() {
const random = options[Math.floor(Math.random() * options.length)];
console.log(random)
return random
}
function playerSelection() {
selectionButtons.forEach(selectionButton => {
selectionButton.addEventListener('click', e => {
const selected = selectionButton.dataset.selection
console.log(selected)
return selected
})
})
}
function computerSelection() {
selectionButtons.forEach(selectionButton => {
selectionButton.addEventListener('click', e => {
computerPlay()
})
})
}
const playerSelected = playerSelection()
const computerSelected = computerSelection()
function playRound() {
if (playerSelected == 'rock' && computerSelected == 'rock' ||
playerSelected == 'paper' && computerSelected == 'paper' ||
playerSelected == 'scissors' && computerSelected == 'scissors') {
console.log('tie')
}
else if (playerSelected == 'rock' && computerSelected == 'scissors' ||
playerSelected == 'paper' && computerSelected == 'rock' ||
playerSelected == 'scissors' && computerSelected == 'paper') {
console.log('player won')
}
else {
console.log('player lose')
}
}
playRound()
* {
font-style: arial;
background-color: lightblue;
margin:0;
padding:0;
}
.scores {
display:grid;
grid-template-columns: repeat(2, 1fr);
justify-items: center;
justify-content: center;
align-items: center;
margin-top: 2rem;
}
.selection {
cursor: pointer;
background-color: red;
font-size: 1rem;
transition:500ms;
}
.selection:hover {
transform: scale(1.3)
}
.header {
text-align: center;
margin-top:0;
font-size: 2rem;
}
.selections {
display: flex;
justify-content: space-around;
margin-top: 5rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<div class="header">
Choose your option:
</div>
<div class="selections">
<button class="selection" data-selection="rock">Rock</button>
<button class="selection" data-selection="paper">Paper</button>
<button class="selection" data-selection="scissors">Scissors</button>
</div>
<div class="scores">
<div>
Player Score
<span>0</span>
</div>
<div>
Computer Score
<span>0</span>
</div>
</body>
</html>
There are a couple issues here. First off, you are calling the playRound() function before any buttons are pressed. It's called on load of the script and then never again. What you'd need to do is to call playRound() inside your click handler because that is the event which you need to test if the user won, lost, or tied.
Secondly, you are trying to return values from a click handler inside a .forEach, neither of which by definition return a value to their caller.
I think your best bet to solve this is to do a couple things:
Move your computerPlay() into your click handler
Move your playRound() into your click handler
Here's an example of what it would look like:
const selectionButtons = document.querySelectorAll('[data-selection]')
const options = ['rock', 'paper', 'scissors']
function computerPlay() {
const random = options[Math.floor(Math.random() * options.length)];
return random
}
selectionButtons.forEach(selectionButton => {
selectionButton.addEventListener('click', e => {
const selected = selectionButton.dataset.selection;
const computerSelected = computerPlay();
console.log("Player Selection: " + selected);
console.log("Computer Selection: " + computerSelected);
playRound(selected, computerSelected);
})
})
function playRound(playerSelected, computerSelected) {
if (playerSelected == 'rock' && computerSelected == 'rock' ||
playerSelected == 'paper' && computerSelected == 'paper' ||
playerSelected == 'scissors' && computerSelected == 'scissors') {
console.log('tie')
}
else if (playerSelected == 'rock' && computerSelected == 'scissors' ||
playerSelected == 'paper' && computerSelected == 'rock' ||
playerSelected == 'scissors' && computerSelected == 'paper') {
console.log('player won')
}
else {
console.log('player lose')
}
}
* {
font-style: arial;
background-color: lightblue;
margin:0;
padding:0;
}
.scores {
display:grid;
grid-template-columns: repeat(2, 1fr);
justify-items: center;
justify-content: center;
align-items: center;
margin-top: 2rem;
}
.selection {
cursor: pointer;
background-color: red;
font-size: 1rem;
transition:500ms;
}
.selection:hover {
transform: scale(1.3)
}
.header {
text-align: center;
margin-top:0;
font-size: 2rem;
}
.selections {
display: flex;
justify-content: space-around;
margin-top: 5rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<div class="header">
Choose your option:
</div>
<div class="selections">
<button class="selection" data-selection="rock">Rock</button>
<button class="selection" data-selection="paper">Paper</button>
<button class="selection" data-selection="scissors">Scissors</button>
</div>
<div class="scores">
<div>
Player Score
<span>0</span>
</div>
<div>
Computer Score
<span>0</span>
</div>
</body>
</html>
Whenever I click a button and the value is inputted, the values in the box goes down. I've tried changing some CSS but it doesn't work as I intend it to.
const game = (() => {
const createPlayer = (name, marker, turn) => {
return { name, marker, turn }
}
player1 = createPlayer('Player 1', "x", true)
player2 = createPlayer('Player 2', "o", false)
const gameBoard = () => {
var gameBoard = []
const movement = (e) => {
if (player1.turn === true && e.target.textContent === "") {
gameBoard[e.target.id] = player1.marker
e.target.textContent = player1.marker
player1.turn = false
} else if (player1.turn === false && e.target.textContent === "") {
gameBoard[e.target.id] = player2.marker
e.target.textContent = player2.marker
player1.turn = true
}
if (gameBoard["button-1"] === "x" && gameBoard["button-2"] === "x" && gameBoard["button-3"] === "x") {
alert("perfect")
} else if (gameBoard["button-4"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-6"] === "x") {
alert("you win")
} else if (gameBoard["button-7"] === "x" && gameBoard["button-8"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-1"] === "x" && gameBoard["button-4"] === "x" && gameBoard["button-7"] === "x") {
alert("you win")
} else if (gameBoard["button-2"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-8"] === "x") {
alert("you win")
} else if (gameBoard["button-3"] === "x" && gameBoard["button-6"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-2"] === "o" && gameBoard["button-3"] === "o") {
alert("perfect")
} else if (gameBoard["button-4"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-6"] === "o") {
alert("you win")
} else if (gameBoard["button-7"] === "o" && gameBoard["button-8"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-4"] === "o" && gameBoard["button-7"] === "o") {
alert("you win")
} else if (gameBoard["button-2"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-8"] === "o") {
alert("you win")
} else if (gameBoard["button-3"] === "o" && gameBoard["button-6"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-3"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-7"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-3"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-7"] === "x") {
alert("you win")
}
}
const board = document.querySelector('.item-board').addEventListener('click', movement)
}
gameBoard()
})
game()
.header {
background-color: red;
background-size: cover;
width: 100%;
height: 100%;
}
.item-board {
margin-left: 200px;
display: inline-block;
}
.TicTac {
background-size: cover;
width: 100%;
height: 100%;
}
.btn-1 {
width: 100px;
height: 100px;
border: 2px solid;
margin: -2px;
font-size: 4rem;
padding: 0px;
}
p {
font-size: 200px;
}
<header class="header">
<h1 class="TicTac">Tic-Tac-Toe</h1>
</header>
<div class="item-board">
<div class="item-board">
<button type="button" name="button" id="button-1" class="btn-1"></button>
<button type="button" name="button" id="button-2" class="btn-1"></button>
<button type="button" name="button" id="button-3" class="btn-1"></button>
</div>
<div class="item-board">
<button type="button" name="button" id="button-4" class="btn-1"></button>
<button type="button" name="button" id="button-5" class="btn-1"></button>
<button type="button" name="button" id="button-6" class="btn-1"></button>
</div>
<div class="item-board">
<button type="button" name="button" id="button-7" class="btn-1"></button>
<button type="button" name="button" id="button-8" class="btn-1"></button>
<button type="button" name="button" id="button-9" class="btn-1"></button>
</div>
</div>
I've tried changing some CSS but can't figure out how to make it so that it doesn't go down on each click.
Your .btn-1 styling is aligning the elements based on their text content, this can be solved by applying vertical-align: top; to that class.
Another small change that's worth making would be to change .item-board from inline-block to display: block, as that will prevent the width of the screen affecting whether the rows wrap.
const game = (() => {
const createPlayer = (name, marker, turn) => {
return { name, marker, turn }
}
player1 = createPlayer('Player 1', "x", true)
player2 = createPlayer('Player 2', "o", false)
const gameBoard = () => {
var gameBoard = []
const movement = (e) => {
if (player1.turn === true && e.target.textContent === "") {
gameBoard[e.target.id] = player1.marker
e.target.textContent = player1.marker
player1.turn = false
} else if (player1.turn === false && e.target.textContent === "") {
gameBoard[e.target.id] = player2.marker
e.target.textContent = player2.marker
player1.turn = true
}
if (gameBoard["button-1"] === "x" && gameBoard["button-2"] === "x" && gameBoard["button-3"] === "x") {
alert("perfect")
} else if (gameBoard["button-4"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-6"] === "x") {
alert("you win")
} else if (gameBoard["button-7"] === "x" && gameBoard["button-8"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-1"] === "x" && gameBoard["button-4"] === "x" && gameBoard["button-7"] === "x") {
alert("you win")
} else if (gameBoard["button-2"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-8"] === "x") {
alert("you win")
} else if (gameBoard["button-3"] === "x" && gameBoard["button-6"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-2"] === "o" && gameBoard["button-3"] === "o") {
alert("perfect")
} else if (gameBoard["button-4"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-6"] === "o") {
alert("you win")
} else if (gameBoard["button-7"] === "o" && gameBoard["button-8"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-4"] === "o" && gameBoard["button-7"] === "o") {
alert("you win")
} else if (gameBoard["button-2"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-8"] === "o") {
alert("you win")
} else if (gameBoard["button-3"] === "o" && gameBoard["button-6"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-9"] === "o") {
alert("you win")
} else if (gameBoard["button-3"] === "o" && gameBoard["button-5"] === "o" && gameBoard["button-7"] === "o") {
alert("you win")
} else if (gameBoard["button-1"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-9"] === "x") {
alert("you win")
} else if (gameBoard["button-3"] === "x" && gameBoard["button-5"] === "x" && gameBoard["button-7"] === "x") {
alert("you win")
}
}
const board = document.querySelector('.item-board').addEventListener('click', movement)
}
gameBoard()
})
game()
.header {
background-color: red;
background-size: cover;
width: 100%;
height: 100%;
}
.item-board {
margin-left: 50px;
display: block; /* This prevents the rows appearing on the same line on wide screens */
}
.TicTac {
background-size: cover;
width: 100%;
height: 100%;
}
.btn-1 {
width: 100px;
height: 100px;
border: 2px solid;
margin: -2px;
font-size: 4rem;
padding: 0px;
vertical-align: top; /* Top will fix the alignment issue when text content is added to the button */
}
p {
font-size: 200px;
}
<header class="header">
<h1 class="TicTac">Tic-Tac-Toe</h1>
</header>
<div class="item-board">
<div class="item-board">
<button type="button" name="button" id="button-1" class="btn-1"></button>
<button type="button" name="button" id="button-2" class="btn-1"></button>
<button type="button" name="button" id="button-3" class="btn-1"></button>
</div>
<div class="item-board">
<button type="button" name="button" id="button-4" class="btn-1"></button>
<button type="button" name="button" id="button-5" class="btn-1"></button>
<button type="button" name="button" id="button-6" class="btn-1"></button>
</div>
<div class="item-board">
<button type="button" name="button" id="button-7" class="btn-1"></button>
<button type="button" name="button" id="button-8" class="btn-1"></button>
<button type="button" name="button" id="button-9" class="btn-1"></button>
</div>
</div>
Try using a grid layout for your board.
I cleaned-up the CSS so that it is easier to follow. I also made the players an array and added a turn counter.
The board element is now passed to the function.
Cell lookup is easier when using indicies.
Cells are now regular <div> elements.
The cell dataset (attributes) holds the marker reference.
The cell occupied check is also easier now.
Cell clicks are delegated by the board.
Hopefully this is much easier to understand. I tried to keep the general structure of your functions.
const game = ((el) => {
const createPlayer = (name, marker) =>
({ name, marker });
let turn = 0;
const players = [
createPlayer('Player 1', 'X'),
createPlayer('Player 2', 'O')
];
const gameBoard = () => {
var gameBoard = [];
const getCell = index => el.querySelector(`.cell:nth-child(${index + 1})`);
const getCells = (...indicies) => indicies.map(getCell);
const allOccupied = (marker, ...indicies) =>
getCells(...indicies).every(cell => cell.dataset.marker === marker);
const checkWin = ({ marker }) =>
allOccupied(marker, 0, 1, 2) /* horiz / top */
|| allOccupied(marker, 3, 4, 5) /* horiz / middle */
|| allOccupied(marker, 5, 7, 8) /* horiz / bottom */
|| allOccupied(marker, 0, 3, 6) /* vert / left */
|| allOccupied(marker, 1, 4, 7) /* vert / middle */
|| allOccupied(marker, 2, 5, 8) /* vert / right */
|| allOccupied(marker, 0, 4, 8) /* diag / negative */
|| allOccupied(marker, 2, 4, 6); /* diag / positive */
const delagateClick = (e) => {
if (e.target.classList.contains('cell')) {
handleMovement(e.target);
}
};
const handleMovement = (cell) => {
const player = players[turn % players.length];
if (!cell.dataset.marker) {
cell.dataset.marker = player.marker;
cell.textContent = player.marker;
if (checkWin(player)) {
setTimeout(() => {
alert(`${player.name} ("${player.marker}") wins`);
}, 100);
}
turn++;
}
};
el.addEventListener('click', delagateClick);
}
gameBoard()
})
game(document.querySelector('.board'));
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial;
background: #000;
color: #EEE;
}
header {
display: flex;
justify-content: center;
align-items: center;
background-color: #C33;
background-size: cover;
width: 100%;
height: 4em;
}
header > div {
font-size: 2em;
font-weight: bold;
}
main {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
background: #222;
width: 100%;
}
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 0.25em;
border: thin solid grey;
background: #444;
padding: 0.25em;
}
.cell {
display: flex;
width: 2em;
height: 2em;
border: thin solid grey;
font-size: 2em;
align-items: center;
justify-content: center;
background: #666;
cursor: pointer;
}
.cell:not([data-marker]):hover {
background: #888;
}
.cell[data-marker] {
cursor: not-allowed;
}
<header>
<div>Tic-Tac-Toe</div>
</header>
<main>
<div class="board">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
</main>
So, this is a simple counter script:
//variables
let counter = document.querySelector('.counter');
let decrementCounter = document.querySelector('.decrement-counter');
let incrementCounter = document.querySelector('.increment-counter');
let count = 0;
//event listeners
decrementCounter.addEventListener('click', minusCounter);
incrementCounter.addEventListener('click', plusCounter);
function plusCounter() {
count++;
counter.innerHTML = count;
if (counter.innerHTML > '0') {
counter.style.color = 'green';
} else if (counter.innerHTML === '0') {
counter.style.color = 'black';
}
}
function minusCounter() {
count--;
counter.innerHTML = count;
if (counter.innerHTML < '0') {
counter.style.color = 'red';
} else if (counter.innerHTML === '0') {
counter.style.color = 'black';
}
}
body {
margin: 0;
padding: 0;
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
}
<body>
<div class="counter">
<p>0</p>
</div>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</body>
(ignore the bad design, It was just for test purpose)
I wanted to do the same script but with a constructor/factory function. Or just with a simple object(encapsulation).
Maybe I missed something essential and that's why I failed, can someone show me an example of each?
As mentioned in the comments it is somewhat unclear what sort of encapsulation you are looking for, but here is a simple refactoring of your code into a function which accepts a container element and returns an object containing references to the set() function which instantiates your timer functionality on the children of that element, and a cleanup() function which removes the instantiation.
It requires that the necessary constituent elements exist within the container, but you could expand on this to either completely build the timer within the function or at least do some checks on the existence of elements so as not to break if they are missing.
const setCounter = (element) => {
//variables
const decrementCounter = element.querySelector('.decrement-counter');
const incrementCounter = element.querySelector('.increment-counter');
const output = element.querySelector('.output');
let count = 0;
function plusCounter () {
count++;
output.innerHTML = count;
if (output.innerHTML > '0') {
output.style.color = 'green';
} else if (output.innerHTML === '0') {
output.style.color = 'black';
}
}
function minusCounter () {
count--;
output.innerHTML = count;
if (output.innerHTML < '0') {
output.style.color = 'red';
} else if (output.innerHTML === '0') {
output.style.color = 'black';
}
}
const set = () => {
decrementCounter.addEventListener('click', minusCounter);
incrementCounter.addEventListener('click', plusCounter);
element.classList.add('active');
}
const cleanup = () => {
decrementCounter.removeEventListener('click', minusCounter);
incrementCounter.removeEventListener('click', plusCounter);
count = 0;
output.innerHTML = "0";
output.style.color = 'black';
element.classList.remove('active');
}
return {
set: set,
cleanup: cleanup,
}
}
const counter = setCounter(document.getElementById('counter-1'));
document.querySelector('.set-counter1').addEventListener('click', counter.set);
document.querySelector('.cleanup-counter1').addEventListener('click', counter.cleanup);
const counter2 = setCounter(document.getElementById('counter-2'));
document.querySelector('.set-counter2').addEventListener('click', counter2.set);
document.querySelector('.cleanup-counter2').addEventListener('click', counter2.cleanup);
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.output {
text-align: center;
background-color: white;
width: 2rem;
margin: 1rem auto;
}
.counter-control {
width: 100vw;
text-align: center;
padding: 8px;
}
.active {
background-color: aquamarine;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css.css" type="text/css">
<title>Document</title>
</head>
<body>
<div id="counter-1" class="counter">
<p class="output">0</p>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</div>
<div class="counter-control">
<button class="set-counter1">Set Counter</button>
<button class="cleanup-counter1">Cleanup Counter</button>
</div>
<div id="counter-2" class="counter">
<p class="output">0</p>
<button class="decrement-counter">Decrement</button>
<button class="increment-counter">Increment</button>
</div>
<div class="counter-control">
<button class="set-counter2">Set Counter</button>
<button class="cleanup-counter2">Cleanup Counter</button>
</div>
</body>
</html>
I have two modules: boardObject and displayController... And one factory function: playerFactory... I want to update arrays with a function from playerFactory, but I get error that is not a function.
const playerFactory = () => {
const playTurn = (event) => {
const id = boardObject.cells.indexOf(event.target);
boardObject.boardArray[id] = currentPlayer;
boardObject.render();
return id;
};
return { playTurn };
};
const displayController = (() => {
const playerOne = 'X';
const playerTwo = 'O'
const gameBoard = document.querySelector(".game_board");
let currentPlayer = playerOne;
const switchPlayer = () => {
currentPlayer = currentPlayer === playerOne ? playerTwo : playerOne;
}
gameBoard.addEventListener("click", (event) => {
if (event.target.classList.contains("cell")) {
if (event.target.textContent === "") {
event.target.textContent = currentPlayer;
switchPlayer();
// const id = boardObject.cells.indexOf(event.target);
// boardObject.boardArray[id] = currentPlayer;
// boardObject.render();
currentPlayer.playTurn(event.target);
}
}
});
})();
What might be the problem?
This might also help if someone want to look at all the code:
// player factory...
const playerFactory = () => {
const playTurn = (event) => {
const id = boardObject.cells.indexOf(event.target);
boardObject.boardArray[id] = currentPlayer;
boardObject.render();
};
return {
playTurn
};
};
// Gameboard object...
const boardObject = (() => {
let boardArray = ['', '', '', '', '', '', '', '', ''];
const cells = Array.from(document.querySelectorAll(".cell"));
// displays the content of the boardArray...
const render = () => {
boardArray.forEach((mark, idx) => {
cells[idx].textContent = boardArray[idx];
});
};
return {
boardArray,
render,
cells
};
})();
// Display controller ...
const displayController = (() => {
const playerOne = 'X';
const playerTwo = 'O'
const gameBoard = document.querySelector(".game_board");
let currentPlayer = playerOne;
const switchPlayer = () => {
currentPlayer = currentPlayer === playerOne ? playerTwo : playerOne;
}
gameBoard.addEventListener("click", (event) => {
if (event.target.classList.contains("cell")) {
if (event.target.textContent === "") {
event.target.textContent = currentPlayer;
switchPlayer();
// const id = boardObject.cells.indexOf(event.target);
// boardObject.boardArray[id] = currentPlayer;
// boardObject.render();
////////
currentPlayer.playTurn(event); // this is the problem, dunno why?!?!?!?
}
}
});
})();
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', sans-serif;
background: #BEE9E8;
}
header {
background: #1B4965;
text-align: center;
color: #62B6CB;
padding-top: 20px;
padding-bottom: 10px;
}
.game_board {
display: grid;
width: 500px;
height: 500px;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
margin: 0 auto;
margin-top: 40px;
border: 5px solid #1B4965;
border-radius: 10px;
}
.cell {
border: 5px solid #1B4965;
display: flex;
justify-content: center;
align-items: center;
background: #62b6cb;
color: #1b4965;
font-size: 4em;
font-weight: bold;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="./style.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght#500&display=swap" rel="stylesheet">
</head>
<body>
<header>
<h1>Tic - Tac - Toe<br>The Odin Project</h1>
</header>
<div class="game_board">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<script src="./index.js" defer></script>
</body>
</html>
currentPlayer is just a string X or O, it doesn't have a playTurn() method.
You need to call playerFactory() to get the object. In my code below, I use
const player = playerFactory();
I'm not really sure why you need this object in the first place; it doesn't maintain any state. But maybe that's something you're planning on adding later.
Additionally, playTurn() needs to be passed the currentPlayer string so it can assign it to the grid element. It's a local variable in gameController so it can't access it directly.
// player factory...
const playerFactory = () => {
const playTurn = (event, currentPlayer) => {
const id = boardObject.cells.indexOf(event.target);
boardObject.boardArray[id] = currentPlayer;
boardObject.render();
};
return {
playTurn
};
};
// Gameboard object...
const boardObject = (() => {
let boardArray = ['', '', '', '', '', '', '', '', ''];
const cells = Array.from(document.querySelectorAll(".cell"));
// displays the content of the boardArray...
const render = () => {
boardArray.forEach((mark, idx) => {
cells[idx].textContent = boardArray[idx];
});
};
return {
boardArray,
render,
cells
};
})();
// Display controller ...
const displayController = (() => {
const player = playerFactory();
const playerOne = 'X';
const playerTwo = 'O'
const gameBoard = document.querySelector(".game_board");
let currentPlayer = playerOne;
const switchPlayer = () => {
currentPlayer = currentPlayer === playerOne ? playerTwo : playerOne;
}
gameBoard.addEventListener("click", (event) => {
if (event.target.classList.contains("cell")) {
if (event.target.textContent === "") {
event.target.textContent = currentPlayer;
switchPlayer();
// const id = boardObject.cells.indexOf(event.target);
// boardObject.boardArray[id] = currentPlayer;
// boardObject.render();
////////
player.playTurn(event, currentPlayer);
}
}
});
})();
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto', sans-serif;
background: #BEE9E8;
}
header {
background: #1B4965;
text-align: center;
color: #62B6CB;
padding-top: 20px;
padding-bottom: 10px;
}
.game_board {
display: grid;
width: 500px;
height: 500px;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
margin: 0 auto;
margin-top: 40px;
border: 5px solid #1B4965;
border-radius: 10px;
}
.cell {
border: 5px solid #1B4965;
display: flex;
justify-content: center;
align-items: center;
background: #62b6cb;
color: #1b4965;
font-size: 4em;
font-weight: bold;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tic Tac Toe</title>
<link rel="stylesheet" href="./style.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght#500&display=swap" rel="stylesheet">
</head>
<body>
<header>
<h1>Tic - Tac - Toe<br>The Odin Project</h1>
</header>
<div class="game_board">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<script src="./index.js" defer></script>
</body>
</html>
I am currently working on an experiment using the Web Speech API. The idea is to use speech recognition and synthesis to improve the user's experience with HTML forms. Keep in mind that the code below is just a concept and has many issues. However, it seems that the speechRecognition API and the speechSynthesis API do not work well together. My desired result is that when an input or button receives focus, the label will be read using speechSynthesis and then it will listen to the user's answer using speechRecognition. That seems to be working as expected.
The issue is, that after the speechRecognition has started for the first time the speechSynthesis get's much lower in volume and somethings just fail (does not read). I have added some logic to abort the recognition on focus loss but this does not seem to work. Has anyone ever ran into this issue? Am I doing something wrong? Any help is welcome.
EDIT: StackOverflow does not ask for the user's mic permissions, please use this fiddle: https://jsfiddle.net/wzt0nfp3/ for a working demo.
const speechRecognition = !!window.SpeechRecognition || !!window.webkitSpeechRecognition ?
new(window.SpeechRecognition || window.webkitSpeechRecognition)() :
false;
const speechSynthesis =
"speechSynthesis" in window ? window.speechSynthesis : false;
if (!!speechRecognition && !!speechSynthesis) {
let voice = null;
speechRecognition.lang = "en-US";
speechRecognition.continuous = false;
speechRecognition.interimResults = false;
const state = {
speaking: false,
listening: false
};
function loadVoice() {
const voices = speechSynthesis.getVoices();
let defaultVoice = null;
let preferredVoice = null;
voices.forEach(voice => {
if (defaultVoice && preferredVoice) return;
if (voice.default) defaultVoice = voice;
if (voice.name.startsWith("Microsoft Jessa Online")) {
preferredVoice = voice;
}
});
voice = preferredVoice ? preferredVoice : defaultVoice;
}
loadVoice();
speechSynthesis.onvoiceschanged = loadVoice;
const abortRecognition = () => speechRecognition.abort();
const startRecognition = () => speechRecognition.start();
function speak(text) {
if (speechSynthesis.speaking) speechSynthesis.cancel();
if (text !== "") {
const utterThis = new SpeechSynthesisUtterance(text);
utterThis.lang = "en-US";
utterThis.voice = voice;
utterThis.volume = 1;
utterThis.pitch = 1;
utterThis.rate = 1;
utterThis.addEventListener("start", event => {
state.speaking = true;
console.log("Start Speaking.", state);
});
utterThis.addEventListener("error", event => {
state.speaking = false;
console.log("Error: " + event.error, state);
});
utterThis.addEventListener("end", event => {
startRecognition();
state.speaking = false;
console.log("Stop Speaking.", state);
});
speechSynthesis.speak(utterThis);
}
}
speechRecognition.addEventListener("start", event => {
state.listening = true;
console.log("Start Listening.", state);
});
speechRecognition.addEventListener("error", event => {
state.listening = false;
console.log("Error: " + event.error, state);
});
speechRecognition.addEventListener("end", event => {
state.listening = false;
console.log("Stop Listening.", state);
});
speechRecognition.addEventListener("result", event => {
if (typeof event.results === "undefined") return;
state.listening = false;
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
const transcript = capitalize(event.results[0][0].transcript.trim());
console.log("Transcript: " + transcript, state);
if (transcript !== "") {
const inputField = document.activeElement;
inputField.value = transcript;
}
});
document.querySelectorAll("input").forEach(input => {
input.addEventListener("blur", () => abortRecognition());
input.addEventListener("focus", e => {
speak(e.target.parentElement.textContent.trim());
});
});
document.querySelectorAll("textarea").forEach(textarea => {
textarea.addEventListener("blur", () => abortRecognition());
textarea.addEventListener("focus", e => {
speak(e.target.parentElement.textContent.trim());
});
});
document.querySelectorAll("button").forEach(button => {
button.addEventListener("blur", () => abortRecognition());
button.addEventListener("focus", e => {
speak(e.target.textContent.trim());
});
});
}
body {
font-family: sans-serif;
margin: 5% 0;
}
form {
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 600px;
width: 90%;
}
label {
align-items: center;
color: blue;
display: flex;
justify-content: space-between;
margin-bottom: 1em;
}
input,
textarea {
border: none;
border-bottom: 1px solid blue;
flex-grow: 1;
font-size: inherit;
margin-left: 1rem;
max-width: 350px;
padding: 0.5rem 0;
}
input:focus,
textarea:focus {
outline: none;
box-shadow: 0 1px 0 0 blue;
}
textarea {
resize: vertical;
}
button {
background-color: blue;
border-radius: 0;
border-radius: 3px;
border: none;
color: white;
font-size: inherit;
margin-top: 2rem;
padding: 0.75rem 5rem;
width: fit-content;
}
button:focus,
button:hover {
background-color: transparent;
border: 2px solid blue;
color: blue;
outline: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Web Speech API</title>
</head>
<body>
<form>
<label>My name is
<input type="text" placeholder="Your name" />
</label>
<label>My business is
<input type="text" placeholder="Company name" />
</label>
<label>You can email me at
<input type="email" placeholder="Email address" />
</label>
<label>You can call me at
<input type="tel" placeholder="Phone number" />
</label>
<label>My project is
<textarea rows="5" placeholder="Description of my project, budget, time constraints..."></textarea>
</label>
<button type="submit">Get in touch</button>
</form>
<script src="./index.js"></script>
</body>
</html>