Tic-Tac-Toe Project: Restart Button Not Resetting Game State [closed]

I’m building a Tic-Tac-Toe game as part of The Odin Project JavaScript curriculum. My implementation works fine in the console version, but I’m facing an issue in the browser version.

function GameBoard() {
  let rows = 3;
  let columns = 3;

  const board = [];

  for(let i = 0; i < rows; i++) {
    board[i] = [];
    for(let j = 0; j < columns; j++) {
      board[i].push(Cell());
    }
  }

  const getBoard = () => board;

  const markCell = (row, col, token) => {
    if(board[row][col].getValue() != 0) return -1; 
    board[row][col].setValue(token);
    return 0;
  };

  const printBoard = () => {
    const boardWithCellValues = board.map((row) => row.map((cell) => cell.getValue()));
    console.log(boardWithCellValues);
  };

  return {
    getBoard,
    markCell,
    printBoard,
  };
}

function Cell() {
  let value = '';

  const setValue = (token) => {
    value = token;
  };

  const getValue = () => value;

  return {
    setValue,
    getValue,
  };
}

function GameController(
  playerOne="Player 1", 
  playerTwo="Player 2"
) {
  let turnsPlayed = 0;
  const players = [
    {
      name: playerOne,
      token: 'X'
    },
    {
      name: playerTwo,
      token: 'O'
    }
  ];
  const board = GameBoard();
  let activePlayer = players[0];

  const switchPlayerTurn = () => {
    activePlayer = activePlayer === players[0] ? players[1] : players[0];
  };

  const getActivePlayer = () => activePlayer;

  const printNewRound = () => {
    board.printBoard();
    console.log(`${getActivePlayer().name}'s turn ...`);
  }

  const checkWin = () => {
    // logic for tic tac toe game
    let hasWon = false;
    let playerToken = getActivePlayer().token;
    const boardArray = board.getBoard();

    let a = boardArray[0][0].getValue();
    let b = boardArray[1][1].getValue();
    let c = boardArray[2][2].getValue();
    if(a === b && b === c && a === playerToken) {
      hasWon = true;
    } else {
      a = boardArray[0][2].getValue();
      b = boardArray[1][1].getValue();
      c = boardArray[2][0].getValue();
      if(a === b && b === c && a === playerToken) {
        hasWon = true;
      } else {
        for(let i = 0; i < boardArray.length; i++) {
          a = boardArray[i][0].getValue();
          b = boardArray[i][1].getValue();
          c = boardArray[i][2].getValue();
          if(a === b && b === c && a === playerToken) {
            hasWon = true;
            break;
          }
    
          a = boardArray[0][i].getValue();
          b = boardArray[1][i].getValue();
          c = boardArray[2][i].getValue();
          if(a === b && b === c && a === playerToken) {
            hasWon = true;
            break;
          }
        }
      }
    }

    return hasWon;
  }

  const checkDraw = () => {
    return turnsPlayed == 9;
  }

  const playRound = (r, c) => {
    console.log(
      `Marking ${getActivePlayer().name}'s token in Cell (${r}, ${c}).`
    );
    const exit_status = board.markCell(r,c, getActivePlayer().token);
    // Do nothing if an occupied cell is marked by player
    if(exit_status == -1) return; 
    turnsPlayed++;
    if(checkWin()) {
      board.printBoard();
      console.log(`${getActivePlayer().name} has won the game.`);
    } else if (checkDraw()) {
      board.printBoard();
      console.log(`It is a Draw. Play again ...`)
    } else {
      switchPlayerTurn();
      printNewRound();
    }
  }

  // Intial Render
  printNewRound();

  return {
    playRound,
    getActivePlayer,
    checkWin,
    checkDraw,
    getBoard: board.getBoard
  };
}


function ScreenController() {
  const game = GameController();
  const playerTurnDiv = document.querySelector('.turn');
  const boardDiv = document.querySelector('.board');

  const updateScreen = () => {
    boardDiv.textContent = '';

    const board = game.getBoard();
    const activePlayer = game.getActivePlayer();

    playerTurnDiv.textContent = `${activePlayer.name}'s Turn`;

    board.forEach((row, row_idx) => {
      row.forEach((cell, col_idx) => {
        const cellButton = document.createElement('button');
        cellButton.classList.add('cell');
        cellButton.textContent = cell.getValue();
        cellButton.dataset.row_idx = row_idx;
        cellButton.dataset.col_idx = col_idx;
        boardDiv.appendChild(cellButton);
      })
    })
  }

  const displayGameOver = () => {
    const msg = document.querySelector('.game-over-message');
    if(game.checkWin()) {
      msg.textContent = `${game.getActivePlayer().name} has won the game.`
    } else {
      msg.textContent = "It is a tie."
    }
    gameOverDialog.showModal();
  }


  function clickHandlerBoard(e) {
    const row = e.target.dataset.row_idx;
    const col = e.target.dataset.col_idx; 

    if(!row) return;

    game.playRound(row, col);
    updateScreen();
    if(game.checkWin() || game.checkDraw())
      displayGameOver();
  }

  boardDiv.addEventListener("click", clickHandlerBoard);

  // Initial Render
  updateScreen();
}

ScreenController();

const gameOverDialog = document.querySelector("#game-over-screen");
const restartBtn = document.querySelector('.restartBtn');

restartBtn.addEventListener('click', () => {
  gameOverDialog.close();
  ScreenController();
});
/*
Josh's Custom CSS Reset
https://www.joshwcomeau.com/css/custom-css-reset/
*/

*, *::before, *::after {
box-sizing: border-box;
}

* {
margin: 0;
}

@media (prefers-reduced-motion: no-preference) {
html {
    interpolate-size: allow-keywords;
}
}

body {
line-height: 1.5;
-webkit-font-smoothing: antialiased;
}

img, picture, video, canvas, svg {
display: block;
max-width: 100%;
}

input, button, textarea, select {
font: inherit;
}

p, h1, h2, h3, h4, h5, h6 {
overflow-wrap: break-word;
}

p {
text-wrap: pretty;
}
h1, h2, h3, h4, h5, h6 {
text-wrap: balance;
}

#root, #__next {
isolation: isolate;
}

/* CSS Starts Here */

.main-container {
    height: 100vh;
    display: flex;
    flex-direction: column;
    font-family: monospace, sans-serif;
}

.header {
    padding: 32px;
    text-align: center;
}

.header h1 {
    font-size: 48px;
}

.footer {
    text-align: center;
    padding: 16px;
}

.container {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 36px 0;
    gap: 1.5em;
}

.container .turn {
    font-size: 24px;
}

.board {
    display: grid;
    height: 500px;
    width: 500px;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    border: 1px solid black;
    gap: 2px;
    padding: 1px;
    background-color: rgb(255, 17, 0);
}
  
.cell {
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 5rem;
    border: 1px solid grey;
    background: lightyellow;
    cursor: pointer;
}

dialog {
    font-size: 2em;
    position: fixed;
    margin: auto;
    width: 45ch;
    /* padding: 1em; */
    border: 3px solid greenyellow;
    border-radius: 8px;
    box-shadow: 0 10px 25px rgba(0,0,0,0.2);
}

dialog::backdrop {
    background: rgba(0, 0, 0, 0.5);
}

.game-over-message {
    margin-bottom: 1.5em;
    text-align: center;
}

.game-over-toolbar {
    display: flex;
    justify-content: center;
}

.restartBtn {
    padding: 0.5em;
    border-radius: 32px;
    border: none;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
    background-color: rgb(173, 255, 47);
    display: flex;
    align-items: center;
    justify-content: center;
}

.restartBtn:hover {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
    cursor: pointer;
}
<div class="main-container">
        <div class="header">
            <h1>Tic Tac Toe</h1>
        </div>
        <div class="container">
            <div class="turn"></div>
            <div class="board"></div>
        </div>
        <div class="footer">&copy; GOTW 2025</div>
    </div>
    <dialog id="game-start-screen">
        <label for="playerOneName">Enter Player 1's name:</label>
        <input type="text" id="playerOneName" autofocus>
    </dialog>
    <dialog id="game-over-screen">
        <div class="game-over-message"></div>
        <div class="game-over-toolbar">
            <button class="restartBtn">&#x21bb; Restart</button>
        </div>
    </dialog>
</body>

Problem:
After a game is won, I display a “Game Over” dialog. I’ve added a Restart button inside this dialog, which is meant to reset the game state and allow a fresh game. However, after clicking restart, although the dialog closes, the game remains stuck — it behaves as if the previous state is still active, and clicking on a cell immediately brings back the “Game Over” dialog.

Here’s the relevant part of the code:

restartBtn.addEventListener('click', () => {   
     gameOverDialog.close();   
     ScreenController(); }); 

Question:
How can I properly reset both the UI and internal game state when restarting the game via the Restart button? Is there a better way to reinitialize the game components?

Any suggestions or patterns for managing game state reset cleanly would be appreciated!

What I expected:

  1. The board should be cleared.
  2. All game state (turns, board data, game over flag) should reset.
  3. The user should be able to play a new game from scratch.

What I’ve tried:

  1. Calling ScreenController() again in the restart handler.
  2. Confirmed that the console version resets just fine.

GoogleAPI, Get User’s Additional Emails

I have a question about GAPI and People API. After the user signs in with Google OAuth I need to collect every email address attached to that Google Account (aliases / secondary addresses).

So what I’ve tried to do is to:

So the question is: is there a way to actually get the additional email addresses from the Google Accounts ? Because I’m lost between Google Docs and their support.

What I already checked

“Google Apps Script stops working after sheet copy – emails not sending and status not updating”

Problem Description:
I have a Google Sheet with an Apps Script that:

  1. Creates new request sheets when a form is submitted
  2. Adds a submit checkbox to each request sheet
  3. When checked, should:
    • Update status in the main sheet
    • Send confirmation emails
    • Mark the request as submitted

This works perfectly in the original sheet, but after making a copy:
The checkbox triggers don’t work
No emails are sent
Status doesn’t update
No error messages appear.

What I’ve Tried:

  1. Reinstalled all triggers using setupCheckboxTrigger()
  2. Reauthorized the script (ran test functions that work)
  3. Verified all sheet references use getActiveSpreadsheet()
  4. Checked execution logs – shows trigger fires but no visible changes
  5. Tested email sending separately – works when run manually

Code Highlights:

Main trigger handler:
javascript

function handleCheckboxSubmit(e) {
  if (!e || !e.value || e.value !== "TRUE") return;
  
  const sheet = e.range.getSheet();
  const range = e.range;
  const row = range.getRow();
  
  // Verify this is our submit checkbox
  if (range.getColumn() !== 2 || 
      sheet.getRange(row, 1).getValue() !== "Submit Request") {
    return;
  }
  // ... rest of the function
}

function submitRequest() {
  // ... gets all required data
  try {
    MailApp.sendEmail({
      to: ${requestorEmail}, ${designerEmail}, ${groupEmail},
      name: senderName,
      subject: subject,
      htmlBody: body
    });
  } catch (e) {
    console.error("Email failed:", e);
  }
}

How to try Translator and Language Detection API in Chrome 138

I would like to use the new Translator and Language Detector APIs in a Greasemonkey script I use for personal-use. Aim is to translate on the fly some sites I usually navigate.
The problem is I noticed they are NOT available on HTTP-only websites, so, when I try to use them in my script Translator and LanguageDetector variable are never defined.
This only happens on HTTP websites, on HTTPS and localhost works fine.

If you navigate with Chrome 138 to an HTTP-only website, for example http://neverssl.com/, and open a console you can check for yourself.

Any suggestion?

electron-builder building issue “can’t detect abi”

I was trying to use electron-builder to build my app and I get an error.
it worked a month ago and I didn’t change anything in the package.json file but now it doesn’t work. The app des work when I open it using “npm start” but I can’t build it now for some reason.

I was trying to use this command in administrator cmd “npx electron-builder –dir”.
A month ago it did work with no issues and now I get an error after the “preparing” stage.

The error: ⨯ Could not detect abi for version 37.1.0 and runtime electron. Updating “node-abi” might help solve this issue if it is a new release of electron failedTask=build stackTrace=Error: Could not detect abi for version 37.1.0 and runtime electron.

I tried deleting node modules and running “npm install” but it didn’t help.
i also tried “npm audit fix” which didn’t help.
I also tried downgrading the version of electron and it also didn’t help.

infinite site loading, no refresh on file save, memory allocation failed

I’m using Vite 6, React, Redux and TailwindCSS v4 for my project. 2 month ago I started and made a lot of progress, then I left it for 2 month to make another one. Now I returned and I my project stopped working.

Primary issues:

  • No auto refresh.
  • Infinite site loading, plain white window.

I start my project by this command:

npm run dev -- --host

package.json

package.json

vite.config.ts

vite config

index.css

index.css

There are some CSS code lower, but it’s irrelevant plain CSS code.

After a lot of troubleshooting I found out that when I remove @import "tailwindcss"; from index.css all issues are fixed. Auto refresh works when files change, and site loads immediately.

But as soon as I return tailwindcss import, everything breaks again. When it breaks, I don’t get any errors, just those two problems occur. And if I remove @import "tailwindcss"; from index.css when project breaks it doesn’t restore. I need to manually stop it and start again by CTRLC and then command npm run dev -- --host.

Also I found out, that if I leave my broken project open for some time, it closes with error in console:

memory allocation of 64424509440 bytes failed

If I understand right, it means that when I connect TailwindCSS it breaks and enters infinite loop trying to get all memory from pc.

The most ridiculous thing about this situation, that 2 month ago everything worked without any error. But now it just randomly broke.

Vite 6.3.5, TailwindCSS 4.1.11: Tailwind crashes project: infinite site loading, no refresh on file save, memory allocation failed

I’m using Vite, React, Redux and TailwindCSS for my project. 2 month ago I started and made a lot of progress, then I left it for 2 month to make another one. Now I returned and I my project stopped working.

Primary issues:
No auto refresh.
Infinite site loading, plain white window.

I start my project by this command: npm run dev — –host

My package.json:

package.json

My vite.config.ts file:

vite config

My index.css file:

index.css

There are some css code lower, but it’s irrelevant plain css code.

After a lot of troubleshooting I found out that when I remove @import “tailwindcss”; from index.css all issues are fixed. Auto refresh works when files change, and site loads immediately.

But as soon as I return tailwindcss import, everything breaks again. When it breaks, I don’t get any errors, just those two problems occur. And if I remove @import “tailwindcss”; from index.css when project breaks it doesn’t restore. I need to manually stop it and start again by “ctrl + c” and then command “npm run dev — –host”

Also I found out, that if I leave my broken project open for some time, it closes with error in console “memory allocation of 64424509440 bytes failed”. If I understand right, it means that when I connect tailwindcss it breaks and enters infinite loop trying to get all memory from pc.

The most ridiculous thing about this situation, that 2 month ago everything worked without any error. But now it just randomly broke.

Using Prettier as beautifier user’s code in Angular project

In my project Prettier is already connected for the project code formatting. I’m trying to use Prettier for user’s code formatting in Ace Editor:

import * as prettier from 'prettier';
import * as babel from 'prettier/plugins/babel';
import * as estree from 'prettier/plugins/estree';
...
const code = this.editor.getValue();
const formatted = prettier.format(code, {
  parser: 'babel',
  plugins: [babel, estree]
});

…but I get an error:

Uncaught TypeError: Super constructor null of anonymous class is not a constructor

I’ve tried different options – nothing has helped yet.
Maybe someone has a successful experience – I would be very grateful.

Angular ReferenceError: document is not defined

Note my knowledge of JS frameworks and TS is essentially non-existent.

I’m trying to create an SPA with SSG. I have a main component I’m trying to display a webAMP in. Which needs to access document. Right now I have the code to create a webAMP object in the main-component.ts. But it’s throwing this error:

ReferenceError: document is not defined
at eval (eval at runInlinedModule (file:.../node_modules/vite/dist/node/module-runner.js:1062:11), <anonymous>:19481:10)
at async ESModulesEvaluator.runInlinedModule (file:.../node_modules/vite/dist/node/module-runner.js:1062:5)
at async SSRCompatModuleRunner.directRequest (file:.../node_modules/vite/dist/node/module-runner.js:1284:61)
at async SSRCompatModuleRunner.directRequest (file:.../node_modules/vite/dist/node/chunks/dep-DBxKXgDP.js:25274:23)
at async SSRCompatModuleRunner.cachedRequest (file:.../node_modules/vite/dist/node/module-runner.js:1180:76)

I think I’ve tried these but found no progress:

main.component.ts

import { NgOptimizedImage } from '@angular/common';
import { Component, DOCUMENT, Inject} from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
import Webamp from 'webamp';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-main',
  imports: [RouterLink, RouterOutlet, NgOptimizedImage, CommonModule],
  templateUrl: './main.component.html',
  styleUrl: './main.component.css',
})

export class MainComponent {
  constructor(@Inject(DOCUMENT) private document: Document) {
    this.musicPlayer();
  }
  musicPlayer() {
    alert("the playerrrr"); // for testing
    const webamp = new Webamp({/* ... */});
    webamp.renderWhenReady(this.document.getElementById('winamp-container') as HTMLElement);
  }
}
}

Laravel Gantt Chart

I’m working on a Laravel-based project management system and need to implement a Gantt chart that includes the following task data:

  1. Task item
  2. Sub-activities (child tasks under a main task)
  3. Planned start date
  4. Planned end date
  5. Actual start date
  6. Actual end date
  7. Progress(%)

Are the recommended libraries or JavaScript plugins that integrate well with Laravel for creating Gantt charts with requirement above? Thanks

transformMixedEsModules not working for mixed ESM/CommonJS modules

I’m having some confusion when using the rollup plugin commonjs.

In my application, some third-party packages in node_modules use CommonJS syntax. By default, I use @rollup/plugin-commonjs to handle this code, and it works as expected.

However, when some modules have mixed ESM and CommonJS usage, it seems the commonjs plugin doesn’t achieve the expected effect, even though I’ve already set the transformMixedEsModules parameter, but it still doesn’t work.

For example, with this config file:

import { defineConfig } from 'rollup';
import commonjs from '@rollup/plugin-commonjs';
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default defineConfig({
  input: 'index.js',
  output: [
    {
      file: 'dist/bundle.esm.js',
      format: 'es'
    }
  ],
  plugins: [
    nodeResolve({
      preferBuiltins: false
    }),
    commonjs({
      transformMixedEsModules: true,
    })
  ],
  external: []
});

The content of index.js is quite simple:

"use strict";

import _reduceInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/reduce";
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.warning = warning;
function warning () {
  console.log('warning')
}

The final output bundle.esm.js still contains CommonJS statements:

import '@babel/runtime-corejs3/core-js-stable/instance/reduce';

Object.defineProperty(exports, "__esModule", {
  value: true
});
// commonjs not working
exports.warning = warning;
function warning () {
  console.log('warning');
}

I’m not sure if I’m using it wrong somewhere? Or how should I properly use it?

Parsing location data from map

I’m working on cartography, collecting locations along with their coordinates, and recording them in an Excel file like this:

Coffee Shop | Longitude: 68.3456 | Latitude: 39.2523

Gathering all these important locations manually takes a lot of time. Therefore, I’ve decided to automate the process by parsing data directly from map services, such as Yandex Maps.

How can I implement automatic parsing of locations from maps like Yandex?