Skip to content
This repository was archived by the owner on Jun 21, 2025. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 390 additions & 0 deletions brno-scrum-tictactoe.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
<!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 - Brno Scrum Workshop</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}

.banner {
width: 100%;
background: linear-gradient(90deg, #ff6b6b 0%, #4ecdc4 50%, #45b7d1 100%);
color: white;
text-align: center;
padding: 20px 0;
font-size: 2.5rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
letter-spacing: 3px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}

.game-container {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 40px;
margin-top: 30px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
backdrop-filter: blur(10px);
}

.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}

.btn {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
border: none;
padding: 12px 24px;
border-radius: 25px;
font-size: 1.1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}

.btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}

.ai-toggle {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.1rem;
color: #333;
}

.toggle-switch {
position: relative;
width: 60px;
height: 30px;
background: #ccc;
border-radius: 15px;
cursor: pointer;
transition: background 0.3s;
}

.toggle-switch.active {
background: #4ecdc4;
}

.toggle-slider {
position: absolute;
top: 3px;
left: 3px;
width: 24px;
height: 24px;
background: white;
border-radius: 50%;
transition: transform 0.3s;
}

.toggle-switch.active .toggle-slider {
transform: translateX(30px);
}

.board {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-template-rows: repeat(3, 100px);
gap: 8px;
margin: 20px auto;
padding: 20px;
background: linear-gradient(135deg, #74b9ff, #0984e3);
border-radius: 15px;
box-shadow: inset 0 4px 8px rgba(0,0,0,0.1);
}

.cell {
width: 100px;
height: 100px;
background: linear-gradient(135deg, #ffffff, #f8f9fa);
border: 3px solid #2d3436;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}

.cell::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
transition: left 0.5s;
}

.cell:hover::before {
left: 100%;
}

.cell:hover {
transform: scale(1.05);
box-shadow: 0 8px 25px rgba(0,0,0,0.2);
}

.cell.X {
color: #e74c3c;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}

.cell.O {
color: #3498db;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}

.status {
text-align: center;
font-size: 1.5rem;
margin: 20px 0;
color: #2d3436;
font-weight: bold;
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}

.winner {
background: linear-gradient(45deg, #00b894, #00cec9);
color: white;
padding: 10px 20px;
border-radius: 25px;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}

@media (max-width: 768px) {
.banner {
font-size: 1.8rem;
padding: 15px 0;
}

.board {
grid-template-columns: repeat(3, 80px);
grid-template-rows: repeat(3, 80px);
}

.cell {
width: 80px;
height: 80px;
font-size: 2.5rem;
}

.game-container {
padding: 20px;
margin: 20px;
}
}
</style>
</head>
<body>
<div class="banner">Brno Scrum Workshop</div>

<div class="game-container">
<div class="controls">
<button class="btn" id="restartBtn">Restart Game</button>
<div class="ai-toggle">
<span>Play vs AI</span>
<div class="toggle-switch" id="aiToggle">
<div class="toggle-slider"></div>
</div>
</div>
</div>

<div class="status" id="status">Player X's turn</div>

<div class="board" id="board">
<div class="cell" data-index="0"></div>
<div class="cell" data-index="1"></div>
<div class="cell" data-index="2"></div>
<div class="cell" data-index="3"></div>
<div class="cell" data-index="4"></div>
<div class="cell" data-index="5"></div>
<div class="cell" data-index="6"></div>
<div class="cell" data-index="7"></div>
<div class="cell" data-index="8"></div>
</div>
</div>

<script>
class TicTacToeGame {
constructor() {
this.board = Array(9).fill('');
this.currentPlayer = 'X';
this.gameActive = true;
this.aiMode = false;
this.cells = document.querySelectorAll('.cell');
this.status = document.getElementById('status');
this.restartBtn = document.getElementById('restartBtn');
this.aiToggle = document.getElementById('aiToggle');

this.winPatterns = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // rows
[0, 3, 6], [1, 4, 7], [2, 5, 8], // columns
[0, 4, 8], [2, 4, 6] // diagonals
];

this.initializeEventListeners();
}

initializeEventListeners() {
this.cells.forEach(cell => {
cell.addEventListener('click', (e) => this.handleCellClick(e));
});

this.restartBtn.addEventListener('click', () => this.restartGame());
this.aiToggle.addEventListener('click', () => this.toggleAI());
}

handleCellClick(e) {
const index = parseInt(e.target.dataset.index);

if (!this.gameActive || this.board[index] !== '') {
return;
}

// Prevent human from playing when it's AI's turn
if (this.aiMode && this.currentPlayer === 'O') {
return;
}

this.makeMove(index, this.currentPlayer);

if (this.gameActive && this.aiMode && this.currentPlayer === 'O') {
setTimeout(() => this.makeAIMove(), 500);
}
}

makeMove(index, player) {
this.board[index] = player;
this.cells[index].textContent = player;
this.cells[index].classList.add(player);

if (this.checkWinner()) {
this.gameActive = false;
this.status.innerHTML = `<span class="winner">Player ${player} wins!</span>`;
return;
}

if (this.checkDraw()) {
this.gameActive = false;
this.status.innerHTML = `<span class="winner">It's a draw!</span>`;
return;
}

this.currentPlayer = this.currentPlayer === 'X' ? 'O' : 'X';
this.updateStatus();
}

makeAIMove() {
if (!this.gameActive) return;

// Simple AI: Try to win, then block, then random
let move = this.findBestMove('O') || this.findBestMove('X') || this.getRandomMove();

if (move !== null) {
this.makeMove(move, 'O');
}
}

findBestMove(player) {
for (let pattern of this.winPatterns) {
let values = pattern.map(index => this.board[index]);
let playerCount = values.filter(val => val === player).length;
let emptyCount = values.filter(val => val === '').length;

if (playerCount === 2 && emptyCount === 1) {
return pattern[values.indexOf('')];
}
}
return null;
}

getRandomMove() {
const emptyCells = this.board.map((cell, index) => cell === '' ? index : null)
.filter(val => val !== null);

if (emptyCells.length === 0) return null;

return emptyCells[Math.floor(Math.random() * emptyCells.length)];
}

checkWinner() {
return this.winPatterns.some(pattern => {
const [a, b, c] = pattern;
return this.board[a] &&
this.board[a] === this.board[b] &&
this.board[a] === this.board[c];
});
}

checkDraw() {
return this.board.every(cell => cell !== '');
}

updateStatus() {
if (this.gameActive) {
let message = `Player ${this.currentPlayer}'s turn`;
if (this.aiMode) {
message = this.currentPlayer === 'X' ? "Your turn (X)" : "AI is thinking...";
}
this.status.textContent = message;
}
}

toggleAI() {
this.aiMode = !this.aiMode;
this.aiToggle.classList.toggle('active');
this.restartGame();
}

restartGame() {
this.board = Array(9).fill('');
this.currentPlayer = 'X';
this.gameActive = true;

this.cells.forEach(cell => {
cell.textContent = '';
cell.classList.remove('X', 'O');
});

this.updateStatus();
}
}

// Initialize the game
const game = new TicTacToeGame();
</script>
</body>
</html>