diff --git a/studium/favicon.ico b/studium/favicon.ico new file mode 100644 index 0000000..9b36118 Binary files /dev/null and b/studium/favicon.ico differ diff --git a/studium/index.html b/studium/index.html new file mode 100644 index 0000000..e69de29 diff --git a/studium/it-lab/images/favicon.ico b/studium/it-lab/images/favicon.ico new file mode 100644 index 0000000..9b36118 Binary files /dev/null and b/studium/it-lab/images/favicon.ico differ diff --git a/studium/it-lab/images/logo.svg b/studium/it-lab/images/logo.svg new file mode 100644 index 0000000..197b65d --- /dev/null +++ b/studium/it-lab/images/logo.svg @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/studium/it-lab/images/symbols.svg b/studium/it-lab/images/symbols.svg new file mode 100644 index 0000000..da5b542 --- /dev/null +++ b/studium/it-lab/images/symbols.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + 6 + 5 + 7 + 8 + + + + + diff --git a/studium/it-lab/index.html b/studium/it-lab/index.html new file mode 100644 index 0000000..d0b5d6a --- /dev/null +++ b/studium/it-lab/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + Minesweeper by Elias Fink + + + + + + + + \ No newline at end of file diff --git a/studium/it-lab/script.js b/studium/it-lab/script.js new file mode 100644 index 0000000..337bb8f --- /dev/null +++ b/studium/it-lab/script.js @@ -0,0 +1,306 @@ +window.addEventListener('load', () => { + minesweeper.init(); +}); + +const minesweeper = { + + init() { + this.logic = remoteLogic; + this.generateBody(); + this.newGame('small'); + }, + + // ------------------------------------ // + // ----- Basic Body Structure ----- // + // ------------------------------------ // + + generateBody() { + const body = document.body; + + const container = document.createElement('div'); + container.id = 'container'; + body.appendChild(container); + + container.append(this.getHeader(), this.getMain(), this.getButtons(), this.getFooter()); + }, + + getHeader() { + const header = document.createElement('header'); + + const title = document.createElement('div'); + title.id = 'title'; + header.appendChild(title); + + const heading = document.createElement('h1'); + heading.innerText = 'Minesweeper'; + + const subheading = document.createElement('p'); + subheading.innerText = 'by Elias Fink'; + + title.append(heading, subheading); + + return header; + }, + + getMain() { + const main = document.createElement('main'); + + return main; + }, + + getButtons() { + const buttons = document.createElement('div'); + buttons.id = 'buttons'; + + const smallButton = this.createButton('small'); + const mediumButton = this.createButton('medium'); + const largeButton = this.createButton('large'); + + buttons.append(smallButton, mediumButton, largeButton); + + return buttons; + }, + + getFooter() { + const footer = document.createElement('footer'); + + const copyright = document.createElement('span'); + copyright.innerHTML = 'Copyright © 2024 Elias Fink'; + + const webLink = document.createElement('span'); + webLink.innerHTML = 'Web Version'; + + const gitLink = document.createElement('span'); + gitLink.innerHTML = 'Git Repository'; + + footer.append(copyright, webLink, gitLink); + + return footer; + }, + + // ---------------------------------- // + // ----- Buttons and Fields ----- // + // ---------------------------------- // + + createButton(type) { + const button = document.createElement('button'); + button.id = type; + button.innerText = type; + + button.addEventListener('click', () => { + this.newGame(type); + }); + + return button; + }, + + generatePlayfield(size) { + const playfield = document.querySelector('main'); + playfield.innerText = ''; + + for (let row = 0; row < size; row++) { + for (let col = 0; col < size; col++) { + playfield.appendChild(this.createCell(row, col)); + } + } + }, + + createCell(row, col) { + const cell = document.createElement('div'); + cell.classList.add('cell', 'covered'); + + cell.dataset.x = col; + cell.dataset.y = row; + + cell.style.width = `calc(100% / ${this.size} - 2px)`; + cell.style.height = `calc(100% / ${this.size} - 2px)`; + + this.cellEventListeners(cell); + + return cell; + }, + + cellEventListeners(cell) { + cell.addEventListener('click', (event) => { + this.cellLeftClickHandler(event); + }); + cell.addEventListener('contextmenu', (event) => { + this.cellRightClickHandler(event); + }); + + cell.addEventListener('touchstart', (event) => { + this.cellTouchStartHandler(event); + }); + cell.addEventListener('touchend', (event) => { + this.cellTouchEndHandler(event); + }); + }, + + // -------------------------- // + // ----- Game Start ----- // + // -------------------------- // + + gameModes: [ + { + name: 'small', + size: 9, + mines: 10 + }, + { + name: 'medium', + size: 16, + mines: 40 + }, + { + name: 'large', + size: 24, + mines: 150 + }, + ], + + async newGame(gameMode) { + for (const mode of this.gameModes) { + if (mode.name == gameMode) { + this.size = mode.size; + this.mines = mode.mines; + } + } + + this.generatePlayfield(this.size); + + await this.logic.init(this.size, this.mines); + }, + + // ------------------------------------ // + // ----- Click/Touch Handling ----- // + // ------------------------------------ // + + async cellLeftClickHandler(event) { + event.preventDefault(); + + const x = event.target.dataset.x; + const y = event.target.dataset.y; + + const result = await this.logic.sweep(x, y); + + if (result.minehit) { + this.logic.gameLost(event, result.mines); + } else { + this.logic.uncoverCell(x, y, result.minesAround); + for (const cell of result.emptyCells) { + this.logic.uncoverCell(cell.x, cell.y, cell.minesAround); + } + if (result.userwins) { + this.logic.gameWon(); + } + } + }, + + cellRightClickHandler(event) { + event.preventDefault(); + + event.target.classList.toggle('symbol-f'); + }, + + cellTouchStartHandler(event) { + event.preventDefault(); + + this.touchStartTime = new Date().getTime(); + }, + + cellTouchEndHandler(event) { + event.preventDefault(); + + const touchDuration = new Date().getTime() - this.touchStartTime; + if (touchDuration < 500) { + this.cellLeftClickHandler(event); + } else { + this.cellRightClickHandler(event); + } + } + +}; + + + + + +// -------------------------------------------------- // +// -------------------------------------------------- // +// -------------------------------------------------- // + + + + + +const remoteLogic = { + + // ----------------------------- // + // ----- Field Filling ----- // + // ----------------------------- // + + async init(size, mines) { + this.serverUrl = 'https://www2.hs-esslingen.de/~melcher/it/minesweeper/?'; + const request = `request=init&size=${size}&mines=${mines}&userid=elfiit00`; + const response = await this.fetchAndDecode(request); + this.token = response.token; + }, + + async fetchAndDecode(request) { + return fetch(this.serverUrl + request).then(response => response.json()); + }, + + // ------------------------------- // + // ----- Move Processing ----- // + // ------------------------------- // + + async sweep(x, y) { + const request = `request=sweep&token=${this.token}&x=${x}&y=${y}`; + return this.fetchAndDecode(request); + }, + + // ------------------------------- // + // ----- Cell Uncovering ----- // + // ------------------------------- // + + getCell(x, y) { + return document.querySelector(`[data-x="${x}"][data-y="${y}"]`); + }, + + uncoverCell(x, y, symbol) { + this.getCell(x, y).classList.remove('covered'); + + if (symbol) { + this.getCell(x, y).classList.add(`symbol-${symbol}`); + } + }, + + // ------------------------ // + // ----- Game End ----- // + // ------------------------ // + + gameLost(event, mines) { + event.target.id = 'mine-hit'; + for (const m of mines) { + this.uncoverCell(m.x, m.y, 'm'); + } + this.displayOverlay('Game Over'); + }, + + gameWon() { + this.displayOverlay('Victory'); + }, + + displayOverlay(text) { + const overlay = document.createElement('div'); + overlay.id = 'overlay'; + + const div = document.createElement('div'); + div.innerText = text; + overlay.appendChild(div); + + const main = document.querySelector('main'); + main.appendChild(overlay); + } + +}; \ No newline at end of file diff --git a/studium/it-lab/styles.css b/studium/it-lab/styles.css new file mode 100644 index 0000000..b641166 --- /dev/null +++ b/studium/it-lab/styles.css @@ -0,0 +1,231 @@ +/* ------------------------- */ +/* General */ +/* ------------------------- */ + +:root { + --blue-1: #001A33; + --blue-2: #004D99; + --blue-3: #0080FF; + --blue-4: #66B3FF; + --blue-5: #CCE6FF; + --grey-1: #161A1D; + --grey-2: #434D56; + --grey-3: #708090; + --grey-4: #A9B3BC; + --grey-5: #E2E6E9; + --peach-fuzz: #ffbe98; +} + +* { + margin: 0; + padding: 0; +} + +html, body { + height: 100%; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} + +#container { + width: min(100vw, 55.555vh); + height: min(100vh, 180vw); + margin: 0 auto; +} + + +/* ------------------------ */ +/* Header */ +/* ------------------------ */ + + +header { + height: calc(4 * 100% / 18); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + background-color: var(--blue-3); + color: #fff; +} + +#title h1 { + font-size: 40px; + margin-bottom: 5px; +} + +#title p { + font-size: 20px; +} + +@media (min-width: 420px) { + + header { + background-image: url(images/logo.svg); + background-size: 100px; + background-position: right 20px center; + background-repeat: no-repeat; + } + +} + + +/* ------------------------------- */ +/* Playing Field */ +/* ------------------------------- */ + +main { + position: relative; + height: calc(10 * 100% / 18); + display: flex; + flex-wrap: wrap; + align-content: space-between; + justify-content: space-between; + border: 1px solid var(--grey-3); +} + +.cell { + flex-shrink: 0; + background-color: var(--blue-5); + border: 1px solid var(--grey-3); +} + +.covered { + background-color: var(--grey-4); +} + +.symbol-1, .symbol-2, .symbol-3, .symbol-4, .symbol-5, .symbol-6, .symbol-7, .symbol-8, .symbol-f.covered, .symbol-m { + background-size: 1000%; + background-image: url(images/symbols.svg); +} + +.symbol-1 { + background-position: calc(100% * 0 / 9); +} + +.symbol-2 { + background-position: calc(100% * 1 / 9); +} + +.symbol-3 { + background-position: calc(100% * 2 / 9); +} + +.symbol-4 { + background-position: calc(100% * 3 / 9); +} + +.symbol-5 { + background-position: calc(100% * 4 / 9); +} + +.symbol-6 { + background-position: calc(100% * 5 / 9); +} + +.symbol-7 { + background-position: calc(100% * 6 / 9); +} + +.symbol-8 { + background-position: calc(100% * 7 / 9); +} + +.symbol-f.covered { + background-position: calc(100% * 8 / 9); +} + +.symbol-m { + background-position: calc(100% * 9 / 9); +} + +#mine-hit { + background-color: var(--peach-fuzz); +} + +#overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.4); +} + +#overlay div { + padding: 16px 32px; + border-radius: 16px; + background-color: var(--blue-4); + font-size: 48px; +} + +#overlay div:hover { + opacity: 0.2; + transition: 0.2s; +} + + +/* ------------------------- */ +/* Buttons */ +/* ------------------------- */ + +#buttons { + height: calc(3 * 100% / 18); + display: flex; + align-items: center; + justify-content: space-around; + padding: 0 20px; + background-color: var(--grey-5); +} + +button { + font-size: 16px; + text-transform: capitalize; + padding: 15px 30px; + margin: 0 2px; + color: #fff; + background-color: var(--blue-3); + border: 2px solid var(--blue-3); + cursor: pointer; + transition: 0.2s; +} + +button:hover { + color: var(--blue-3); + background-color: transparent; +} + + +/* ------------------------ */ +/* Footer */ +/* ------------------------ */ + +footer { + height: calc(100% / 18); + display: flex; + align-items: center; + justify-content: space-around; + padding: 0 20px; + background-color: var(--grey-2); + color: #fff; +} + +footer a { + color: #fff; + text-decoration: none; + border-bottom: 1px solid #fff; +} + +footer a:hover { + border-bottom: 0; +} + +@media (max-width: 480px) { + + footer span:not(:first-child) { + display: none; + } + +} \ No newline at end of file