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 @@
+
+
+
+
+
+ ')
+ .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