diff --git a/Games/Align_4_Game/Readme.md b/Games/Align_4_Game/Readme.md new file mode 100644 index 0000000000..635478339c --- /dev/null +++ b/Games/Align_4_Game/Readme.md @@ -0,0 +1,30 @@ +# **ALIEN_SHOOTERS** + +--- + +
+ +## **Description 📃** +- The logic of the game is basically there is computer and player so we are the player the main task of the player is to stop computer to align 4 balls horizontally or vertically or zigzagcally straight order. + + +## **functionalities 🎮** +- Easy to play +- Scoring system so that you can compare points with computer. +- Responsive design for most of the monitors. +
+ +## **How to play? 🕹ī¸** +- Start the game by clicking on start and then press the grid. +- Your aim is to stop the computer to making or aligning 4 balls in a line . +
+ +## **Screenshots 📸** + +
+ +![Game image](../../assets/images/Align_4_Game.png) + +
+ + diff --git a/Games/Align_4_Game/index.html b/Games/Align_4_Game/index.html new file mode 100644 index 0000000000..b9dee26252 --- /dev/null +++ b/Games/Align_4_Game/index.html @@ -0,0 +1,81 @@ + + + + + + + Align 4 Game + + + + + + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/Games/Align_4_Game/scripts/index.js b/Games/Align_4_Game/scripts/index.js new file mode 100644 index 0000000000..b31f45eac3 --- /dev/null +++ b/Games/Align_4_Game/scripts/index.js @@ -0,0 +1,253 @@ +// constants +const WEB_WORKER_URL = 'scripts/worker.js'; +const BLURBS = { + 'start': { + header: 'Get Ready', + blurb: 'Select your difficulty and start the game.' + }, + 'p1-turn': { + header: 'Your Turn', + blurb: 'Click on the board to drop your chip.' + }, + 'p2-turn': { + header: 'Computer\'s Turn', + blurb: 'The computer is trying to find the best way to make you lose.' + }, + 'p1-win': { + header: 'You Win', + blurb: 'You are a winner. Remember this moment. Carry it with you, forever.' + }, + 'p2-win': { + header: 'Computer Wins', + blurb: 'Try again when you\'re done wiping your tears of shame.' + }, + 'tie': { + header: 'Tie', + blurb: 'Everyone\'s a winner! Or loser. Depends on how you look at it.' + } +}; +const OUTLOOKS = { + 'win-imminent': 'Uh oh, computer is feeling saucy!', + 'loss-imminent': 'Computer is unsure. Now\'s your chance!' +}; + +// global variables +var worker; +var currentGameState; + +// document ready +$(function() { + $('.start button').on('click', startGame); + setBlurb('start'); + setOutlook(); + + // create worker + worker = new Worker(WEB_WORKER_URL); + worker.addEventListener('message', function(e) { + switch(e.data.messageType) { + case 'reset-done': + startHumanTurn(); + break; + case 'human-move-done': + endHumanTurn(e.data.coords, e.data.isWin, e.data.winningChips, e.data.isBoardFull); + break; + case 'progress': + updateComputerTurn(e.data.col); + break; + case 'computer-move-done': + endComputerTurn(e.data.coords, e.data.isWin, e.data.winningChips, e.data.isBoardFull, + e.data.isWinImminent, e.data.isLossImminent); + break; + } + }, false); +}); + +function setBlurb(key) { + $('.info h2').text(BLURBS[key].header); + $('.info .blurb').text(BLURBS[key].blurb); +} + +function setOutlook(key) { + var $outlook = $('.info .outlook'); + if(key) { + $outlook + .text(OUTLOOKS[key]) + .show(); + } else { + $outlook.hide(); + } +} + +function startGame() { + $('.dif').addClass('freeze'); + $('.dif input').prop('disabled', true); + $('.lit-cells, .chips').empty(); + + worker.postMessage({ + messageType: 'reset', + }); +} + +function startHumanTurn() { + setBlurb('p1-turn'); + $('.click-columns div').addClass('hover'); + + // if mouse is already over a column, show cursor chip there + var col = $('.click-columns div:hover').index(); + if(col !== -1) { + createCursorChip(1, col); + } + + $('.click-columns') + .on('mouseenter', function() { + var col = $('.click-columns div:hover').index(); + createCursorChip(1, col); + }) + .on('mouseleave', function() { + destroyCursorChip(); + }); + + $('.click-columns div') + .on('mouseenter', function() { + var col = $(this).index(); + moveCursorChip(col); + }) + .on('click', function() { + $('.click-columns, .click-columns div').off(); + + var col = $(this).index(); + worker.postMessage({ + messageType: 'human-move', + col: col + }); + }); +} + +function endHumanTurn(coords, isWin, winningChips, isBoardFull) { + $('.click-columns div').removeClass('hover'); + if(!coords) { + // column was full, human goes again + startHumanTurn(); + } else { + dropCursorChip(coords.row, function() { + if(isWin) { + endGame('p1-win', winningChips); + } else if(isBoardFull) { + endGame('tie'); + } else { + // pass turn to computer + startComputerTurn(); + } + }); + } +} + +function startComputerTurn() { + setBlurb('p2-turn'); + + // computer's cursor chip starts far left and moves right as it thinks + createCursorChip(2, 0); + + var maxDepth = parseInt($('input[name=dif-options]:checked').val(), 10) + 1; + worker.postMessage({ + messageType: 'computer-move', + maxDepth: maxDepth + }); +} + +function updateComputerTurn(col) { + moveCursorChip(col); +} + +function endComputerTurn(coords, isWin, winningChips, isBoardFull, isWinImminent, isLossImminent) { + moveCursorChip(coords.col, function() { + dropCursorChip(coords.row, function() { + if (isWin) { + endGame('p2-win', winningChips); + } else if (isBoardFull) { + endGame('tie'); + } else { + if(isWinImminent) { + setOutlook('win-imminent'); + } else if (isLossImminent) { + setOutlook('loss-imminent'); + } else { + setOutlook(); + } + + // pass turn to human + startHumanTurn(); + } + }); + }); +} + +function endGame(blurbKey, winningChips) { + $('.dif').removeClass('freeze'); + $('.dif input').prop('disabled', false); + setBlurb(blurbKey); + setOutlook(); + + if(winningChips) { + // not a tie, highlight the chips in the winning run + for(var i = 0; i < winningChips.length; i++) { + createLitCell(winningChips[i].col, winningChips[i].row); + } + } +} + +function createLitCell(col, row) { + $('
') + .css({ + 'left': indexToPixels(col), + 'bottom': indexToPixels(row) + }) + .appendTo('.lit-cells'); +} + +function createCursorChip(player, col) { + var playerClass = 'p' + player; + $('
', { 'class': 'cursor ' + playerClass }) + .css('left', indexToPixels(col)) + .appendTo('.chips'); + + // also highlight column + $('.lit-columns div').eq(col).addClass('lit'); +} + +function destroyCursorChip() { + $('.chips .cursor').remove(); + $('.lit-columns .lit').removeClass('lit'); +} + +function moveCursorChip(col, callback) { + $('.chips .cursor').css('left', indexToPixels(col)); + $('.lit-columns .lit').removeClass('lit'); + $('.lit-columns div').eq(col).addClass('lit'); + + // callback is only used when the computer is about to drop a chip + // give it a slight delay for visual interest + setTimeout(callback, 300); +} + +function dropCursorChip(row, callback) { + // speed of animation depends on how far the chip has to drop + var ms = (7 - row) * 40; + var duration = (ms / 1000) + 's'; + + $('.chips .cursor') + .removeClass('cursor') + .css({ + 'bottom': indexToPixels(row), + 'transition-duration': duration, + 'animation-delay': duration + }) + .addClass('dropped'); + + $('.lit-columns .lit').removeClass('lit'); + setTimeout(callback, ms); +} + +function indexToPixels(index) { + return (index * 61 + 1) + 'px'; +} \ No newline at end of file diff --git a/Games/Align_4_Game/scripts/worker.js b/Games/Align_4_Game/scripts/worker.js new file mode 100644 index 0000000000..09a379f74c --- /dev/null +++ b/Games/Align_4_Game/scripts/worker.js @@ -0,0 +1,236 @@ +// constants +const TOTAL_COLUMNS = 7; +const TOTAL_ROWS = 7; +const HUMAN_WIN_SCORE = -4; +const COMPUTER_WIN_SCORE = 4; +const NO_WIN_SCORE = 0; + +// global variables +var currentGameState; + +// game state object +var GameState = function(cloneGameState) { + this.board = []; + this.score = NO_WIN_SCORE; + this.winningChips = undefined; + + // initialize an empty board + for(var col = 0; col < TOTAL_COLUMNS; col++) { + this.board[col] = []; + } + + // clone from existing game state (if one was passed in) + if(cloneGameState) { + for (var col = 0; col < TOTAL_COLUMNS; col++) { + for (var row = 0; row < cloneGameState.board[col].length; row++) { + this.board[col][row] = cloneGameState.board[col][row]; + } + } + this.score = cloneGameState.score; + } +} +GameState.prototype.makeMove = function(player, col) { + var coords = undefined; + var row = this.board[col].length; + if (row < TOTAL_ROWS) { + this.board[col][row] = player; + this.setScore(player, col, row); + coords = { col: col, row: row }; + } + return coords; +} +GameState.prototype.isBoardFull = function() { + for (var col = 0; col < TOTAL_COLUMNS; col++) { + if (this.board[col].length < TOTAL_ROWS) { + // found an unfilled column + return false; + } + } + return true; +} +GameState.prototype.setScore = function(player, col, row) { + var isWin = + this.checkRuns(player, col, row, 0, 1) || // vertical + this.checkRuns(player, col, row, 1, 0) || // horizontal + this.checkRuns(player, col, row, 1, 1) || // diagonal "/" + this.checkRuns(player, col, row, 1, -1) // diagonal "\" + + if(isWin) { + this.score = (player === 1) ? HUMAN_WIN_SCORE : COMPUTER_WIN_SCORE; + } else { + this.score = NO_WIN_SCORE; + } +} +GameState.prototype.checkRuns = function(player, col, row, colStep, rowStep) { + var runCount = 0; + + // check from 3 chips before to 3 chips after the specified chip + // this covers all possible runs of 4 chips that include the specified chip + for (var step = -3; step <= 3; step++) { + if (this.getPlayerForChipAt(col + step * colStep, row + step * rowStep) === player) { + runCount++; + if (runCount === 4) { + // winning run, step backwards to find the chips that make up this run + this.winningChips = []; + for(var backstep = step; backstep >= step - 3; backstep--) { + this.winningChips.push({ + col: col + backstep * colStep, + row: row + backstep * rowStep + }); + } + return true; + } + } else { + runCount = 0; + if(step === 0) { + // no room left for a win + break; + } + } + } + + // no winning run found + return false; +} +GameState.prototype.getPlayerForChipAt = function(col, row) { + var player = undefined; + if (this.board[col] !== undefined && this.board[col][row] !== undefined) { + player = this.board[col][row]; + } + return player; +} +GameState.prototype.isWin = function() { + return (this.score === HUMAN_WIN_SCORE || this.score === COMPUTER_WIN_SCORE); +} + +// listen for messages from the main thread +self.addEventListener('message', function(e) { + switch(e.data.messageType) { + case 'reset': + resetGame(); + break; + case 'human-move': + makeHumanMove(e.data.col); + break; + case 'computer-move': + makeComputerMove(e.data.maxDepth); + break; + } +}, false); + +function resetGame() { + currentGameState = new GameState(); + + self.postMessage({ + messageType: 'reset-done' + }); +} + +function makeHumanMove(col) { + // coords is undefined if the move is invalid (column is full) + var coords = currentGameState.makeMove(1, col); + var isWin = currentGameState.isWin(); + var winningChips = currentGameState.winningChips; + var isBoardFull = currentGameState.isBoardFull(); + self.postMessage({ + messageType: 'human-move-done', + coords: coords, + isWin: isWin, + winningChips: winningChips, + isBoardFull: isBoardFull + }); +} + +function makeComputerMove(maxDepth) { + var col; + var isWinImminent = false; + var isLossImminent = false; + for (var depth = 0; depth <= maxDepth; depth++) { + var origin = new GameState(currentGameState); + var isTopLevel = (depth === maxDepth); + + // fun recursive AI stuff kicks off here + var tentativeCol = think(origin, 2, depth, isTopLevel); + if (origin.score === HUMAN_WIN_SCORE) { + // AI realizes it can lose, thinks all moves suck now, keep move picked at previous depth + // this solves the "apathy" problem + isLossImminent = true; + break; + } else if (origin.score === COMPUTER_WIN_SCORE) { + // AI knows how to win, no need to think deeper, use this move + // this solves the "cocky" problem + col = tentativeCol; + isWinImminent = true; + break; + } else { + // go with this move, for now at least + col = tentativeCol; + } + } + + var coords = currentGameState.makeMove(2, col); + var isWin = currentGameState.isWin(); + var winningChips = currentGameState.winningChips; + var isBoardFull = currentGameState.isBoardFull(); + self.postMessage({ + messageType: 'computer-move-done', + coords: coords, + isWin: isWin, + winningChips: winningChips, + isBoardFull: isBoardFull, + isWinImminent: isWinImminent, + isLossImminent: isLossImminent + }); +} + +function think(node, player, recursionsRemaining, isTopLevel) { + var scoreSet = false; + var childNodes = []; + + // consider each column as a potential move + for (var col = 0; col < TOTAL_COLUMNS; col++) { + if(isTopLevel) { + self.postMessage({ + messageType: 'progress', + col: col + }); + } + + // make sure column isn't already full + var row = node.board[col].length; + if (row < TOTAL_ROWS) { + // create new child node to represent this potential move + var childNode = new GameState(node); + childNode.makeMove(player, col); + childNodes[col] = childNode; + + if(!childNode.isWin() && recursionsRemaining > 0) { + // no game stopping win and there are still recursions to make, think deeper + var nextPlayer = (player === 1) ? 2 : 1; + think(childNode, nextPlayer, recursionsRemaining - 1); + } + + if (!scoreSet) { + // no best score yet, just go with this one for now + node.score = childNode.score; + scoreSet = true; + } else if (player === 1 && childNode.score < node.score) { + // assume human will always pick the lowest scoring move (least favorable to computer) + node.score = childNode.score; + } else if (player === 2 && childNode.score > node.score) { + // computer should always pick the highest scoring move (most favorable to computer) + node.score = childNode.score; + } + } + } + + // collect all moves tied for best move and randomly pick one + var candidates = []; + for (var col = 0; col < TOTAL_COLUMNS; col++) { + if (childNodes[col] != undefined && childNodes[col].score === node.score) { + candidates.push(col); + } + } + var moveCol = candidates[Math.floor(Math.random() * candidates.length)]; + return moveCol; +} \ No newline at end of file diff --git a/Games/Align_4_Game/styles/index.processed.css b/Games/Align_4_Game/styles/index.processed.css new file mode 100644 index 0000000000..f33b212fd4 --- /dev/null +++ b/Games/Align_4_Game/styles/index.processed.css @@ -0,0 +1,236 @@ +html { + display: table; + width: 100%; + height: 100%; + } + + body { + display: table-row; + background: #000 radial-gradient(1000px 500px, #3f546b, #000); + } + + .wrapper { + display: table-cell; + vertical-align: middle; + text-align: center; + } + + .content { + display: inline-block; + width: 668px; + margin: 0 auto; + padding: 10px 20px; + } + + .sidebar { + float: left; + margin-right: 20px; + width: 220px; + text-align: left; + font-family: 'Doppio One', sans-serif; + color: #ccc; + } + + h1, + h2 { + color: #fff; + margin: 0; + font-weight: normal; + } + + h1 { + height: 68px; + line-height: 68px; + font-size: 40px; + text-align: right; + } + + h2 { + font-size: 18px; + } + + .panel { + padding: 12px; + margin-bottom: 20px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 8px; + } + + .dif-options { + clear: both; + overflow: hidden; + margin: 20px -7px 0; + } + + .dif-options div { + float: left; + width: 20%; + } + + .dif-options input { + display: none; + } + + .dif-options input:checked+label { + color: #fff; + background-color: #593f6b; + border-color: #fff; + cursor: default; + } + + .dif-options label { + display: block; + margin: 0 auto; + width: 24px; + height: 24px; + background-color: #666; + border: solid 2px #ccc; + border-radius: 8px; + color: #999; + text-align: center; + line-height: 24px; + cursor: pointer; + } + + .freeze .dif-options input:not(:checked)+label { + font-size: 0; + margin: 7px auto; + width: 10px; + height: 10px; + border-radius: 4px; + color: transparent; + line-height: 10px; + cursor: default; + transition: .2s; + } + + .start { + margin-top: 20px; + } + + .start button { + display: block; + width: 100%; + padding: 2px 12px 4px; + font-family: inherit; + font-size: 24px; + border: solid 2px #ccc; + border-radius: 8px; + background-color: #593f6b; + color: #fff; + cursor: pointer; + } + + .start button:focus { + outline: none; + } + + .freeze .start { + display: none; + } + + .info div { + margin-top: 10px; + } + + .board { + position: relative; + float: left; + width: 428px; + height: 428px; + margin-top: 68px; + background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/77020/board.png"); + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.4); + } + + .lit-columns, + .lit-cells, + .chips, + .click-columns { + position: absolute; + width: 428px; + height: 428px; + } + + .lit-columns div { + float: left; + width: 60px; + height: 426px; + margin: 1px 0 1px 1px; + transition: background-color 0.1s; + } + + .lit-columns .lit { + background-color: rgba(255, 255, 255, 0.1); + transition: background-color 0.1s; + } + + .lit-cells div { + position: absolute; + width: 60px; + height: 60px; + background-color: rgba(255, 255, 255, 0.3); + } + + .chips div { + position: absolute; + width: 60px; + height: 60px; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + } + + .chips .p1 { + background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/77020/p1-chip.png"); + } + + .chips .p2 { + background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/77020/p2-chip.png"); + } + + .chips .cursor { + bottom: 428px; + transition: left 0.1s ease-out; + } + + .chips .dropped { + transition: bottom ease-in; + animation: bounce 0.3s; + } + + .click-columns div { + float: left; + width: 61px; + height: 428px; + } + + .click-columns div:first-child { + padding-left: 1px; + } + + .click-columns .hover { + cursor: pointer; + } + + @keyframes bounce { + 0% { + animation-timing-function: ease-out; + transform: translateY(0); + } + 30% { + animation-timing-function: ease-in; + transform: translateY(-40px); + } + 60% { + animation-timing-function: ease-out; + transform: translateY(0); + } + 80% { + animation-timing-function: ease-in; + transform: translateY(-10px); + } + 100% { + animation-timing-function: ease-out; + transform: translateY(0); + } + } \ No newline at end of file diff --git a/Games/Align_4_Game/styles/index.scss b/Games/Align_4_Game/styles/index.scss new file mode 100644 index 0000000000..eba1709aeb --- /dev/null +++ b/Games/Align_4_Game/styles/index.scss @@ -0,0 +1,223 @@ +$asset-path: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/77020/'; + +html { + display: table; + width: 100%; + height: 100%; +} + +body { + display: table-row; + background: #000 radial-gradient(1000px 500px, #3f546b, #000); +} + +.wrapper { + display: table-cell; + vertical-align: middle; + text-align: center; +} + +.content { + display: inline-block; + width: 668px; + margin: 0 auto; + padding: 10px 20px; +} + +.sidebar { + float: left; + margin-right: 20px; + width: 220px; + text-align: left; + font-family: 'Doppio One', sans-serif; + color: #ccc; +} + +h1, h2 { + color: #fff; + margin: 0; + font-weight: normal; +} + +h1 { + height: 68px; + line-height: 68px; + font-size: 40px; + text-align: right; +} + +h2 { + font-size: 18px; +} + +.panel { + padding: 12px; + margin-bottom: 20px; + background-color: rgba(255, 255, 255, 0.1); + border-radius: 8px; +} + +.dif-options { + clear: both; + overflow: hidden; + margin: 20px -7px 0; + + div { + float: left; + width: 20%; + } + + input { + display: none; + + &:checked + label { + color: #fff; + background-color: #593f6b; + border-color: #fff; + cursor: default; + } + } + + label { + display: block; + margin: 0 auto; + width: 24px; + height: 24px; + background-color: #666; + border: solid 2px #ccc; + border-radius: 8px; + color: #999; + text-align: center; + line-height: 24px; + cursor: pointer; + } +} + +.freeze .dif-options input:not(:checked) + label { + font-size: 0; + margin: 7px auto; + width: 10px; + height: 10px; + border-radius: 4px; + color: transparent; + line-height: 10px; + cursor: default; + transition: .2s; +} + +.start { + margin-top: 20px; + + button { + display: block; + width: 100%; + padding: 2px 12px 4px; + font-family: inherit; + font-size: 24px; + border: solid 2px #ccc; + border-radius: 8px; + background-color: #593f6b; + color: #fff; + cursor: pointer; + + &:focus { + outline: none; + } + } +} + +.freeze .start { + display: none; +} + +.info div { + margin-top: 10px; +} + +.board { + position: relative; + float: left; + width: 428px; + height: 428px; + margin-top: 68px; + background-image: url($asset-path + 'board.png'); + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.4); +} + +.lit-columns, .lit-cells, .chips, .click-columns { + position: absolute; + width: 428px; + height: 428px; +} + +.lit-columns { + div { + float: left; + width: 60px; + height: 426px; + margin: 1px 0 1px 1px; + transition: background-color 0.1s; + } + + .lit { + background-color: rgba(255, 255, 255, 0.1); + transition: background-color 0.1s; + } +} + +.lit-cells div { + position: absolute; + width: 60px; + height: 60px; + background-color: rgba(255, 255, 255, 0.3); +} + +.chips { + div { + position: absolute; + width: 60px; + height: 60px; + backface-visibility: hidden; + } + + .p1 { + background-image: url($asset-path + 'p1-chip.png'); + } + + .p2 { + background-image: url($asset-path + 'p2-chip.png'); + } + + .cursor { + bottom: 428px; + transition: left 0.1s ease-out; + } + .dropped { + transition: bottom ease-in; + animation: bounce 0.3s; + } +} + +.click-columns { + div { + float: left; + width: 61px; + height: 428px; + + &:first-child { + padding-left: 1px; + } + } + + .hover { + cursor: pointer; + } +} + +@keyframes bounce { + 0% { animation-timing-function: ease-out; transform: translateY(0); } + 30% { animation-timing-function: ease-in; transform: translateY(-40px); } + 60% { animation-timing-function: ease-out; transform: translateY(0); } + 80% { animation-timing-function: ease-in; transform: translateY(-10px); } + 100% { animation-timing-function: ease-out; transform: translateY(0); } +} diff --git a/assets/images/Align_4.webp b/assets/images/Align_4.webp new file mode 100644 index 0000000000..9086bbf3db Binary files /dev/null and b/assets/images/Align_4.webp differ diff --git a/assets/images/Align_4_Game.png b/assets/images/Align_4_Game.png new file mode 100644 index 0000000000..cce7d01352 Binary files /dev/null and b/assets/images/Align_4_Game.png differ