├── .gitignore ├── README.md ├── src ├── main.js ├── modules │ ├── point.js │ ├── piece.js │ └── legion_solver.js ├── locales │ ├── index.js │ ├── tw.js │ ├── cn.js │ ├── ja.js │ ├── ko.js │ └── en.js ├── styles.css ├── index.html ├── i18n.js ├── pieces.js └── board.js ├── .github └── workflows │ └── ci.yml ├── package.json ├── webpack.config.dev.cjs └── webpack.config.prod.cjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | https://xenogents.github.io/LegionSolver/ 2 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import './styles.css'; 2 | import './board.js'; 3 | import './pieces.js'; 4 | import './i18n.js'; 5 | -------------------------------------------------------------------------------- /src/modules/point.js: -------------------------------------------------------------------------------- 1 | class Point { 2 | constructor(x, y) { 3 | this.x = x; 4 | this.y = y; 5 | } 6 | } 7 | 8 | export { Point }; 9 | -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | import en from './en.js'; 2 | import ko from './ko.js'; 3 | import ja from './ja.js'; 4 | import tw from './tw.js'; 5 | import cn from './cn.js'; 6 | 7 | export default { 8 | GMS: en, 9 | KMS: ko, 10 | JMS: ja, 11 | TMS: tw, 12 | CMS: cn, 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | persist-credentials: false 14 | 15 | - name: Install and Build 16 | run: | 17 | npm install 18 | npm run build 19 | 20 | - name: Deploy 21 | uses: JamesIves/github-pages-deploy-action@v4 22 | with: 23 | folder: dist/prod 24 | branch: gh-pages 25 | clean: true 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "legion_solver", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "private": true, 7 | "author": "", 8 | "license": "ISC", 9 | "dependencies": { 10 | "lodash": "^4.17.20" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.12.10", 14 | "@babel/plugin-proposal-class-properties": "^7.12.1", 15 | "@babel/preset-env": "^7.12.11", 16 | "@webpack-cli/serve": "^1.2.2", 17 | "babel-loader": "^8.2.2", 18 | "css-loader": "^5.0.1", 19 | "html-webpack-plugin": "^4.5.1", 20 | "rimraf": "^3.0.2", 21 | "style-loader": "^2.0.0", 22 | "webpack": "^5.17.0", 23 | "webpack-cli": "^4.4.0", 24 | "webpack-dev-server": "^3.11.2" 25 | }, 26 | "scripts": { 27 | "dev": "npx webpack serve --config webpack.config.dev.cjs", 28 | "build": "npm run clean && npx webpack --config webpack.config.prod.cjs", 29 | "clean": "rimraf dist/prod" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.dev.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const languages = ['GMS', 'KMS', 'JMS', 'TMS', 'CMS']; 5 | 6 | module.exports = { 7 | devServer: { 8 | compress: true, 9 | contentBase: path.join(__dirname, 'dist'), 10 | open: true, 11 | watchContentBase: true 12 | }, 13 | entry: './src/main.js', 14 | output: { 15 | filename: 'bundle.js' 16 | }, 17 | mode: 'development', 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | exclude: /node_modules/, 23 | loader: 'babel-loader', 24 | options: { 25 | presets: [{'plugins': ['@babel/plugin-proposal-class-properties']}] 26 | } 27 | }, 28 | { 29 | test: /\.css$/, 30 | use: [ 31 | 'style-loader', 32 | 'css-loader' 33 | ] 34 | } 35 | ] 36 | }, 37 | plugins: [ 38 | new HtmlWebpackPlugin({ 39 | template: 'src/index.html', 40 | inject: false, 41 | languages 42 | }), 43 | ] 44 | }; 45 | -------------------------------------------------------------------------------- /src/locales/tw.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '聯盟戰地排序工具', 3 | instructions: '說明:', 4 | instructionsSub1: '1. 點選你所需要的格子,也可以使用區塊選擇以直接選取整個區塊。', 5 | instructionsSub2: '2. 輸入你擁有的拼圖數量。', 6 | instructionsSub3: '3. 確認可使用區塊數及可填入區塊數相等,否則程式將會無限循環。', 7 | instructionsSub4: '4. 點擊開始程式將使用你輸入的拼圖填滿區塊,點擊觀看過程可以觀看程式計算的過程。', 8 | 9 | spacesToBeFilled: '可填入區塊數: ', 10 | boardSpacesFilled: '可使用區塊數: ', 11 | currentCaracterCountFiled : "當前使用的角色數量: ", 12 | iterations: '嘗試次數: ', 13 | time: '花費時間: ', 14 | 15 | bigClick: '區塊選擇', 16 | liveSolve: '觀看過程', 17 | darkMode: '深色模式', 18 | 19 | start: '開始', 20 | pause: '暫停', 21 | continue: '繼續', 22 | reset: '重置', 23 | clearPieces: '清除擁有的拼圖', 24 | clearBoard: '清除面板', 25 | failText: '找不到結果', 26 | 27 | /** jobs */ 28 | // Lvl: 60 29 | lvl60: 'Lvl 60', 30 | 31 | // Lvl: 100 32 | lvl100: 'Lvl 100', 33 | 34 | // Lvl: 140 35 | warriorPirate140: 'Lvl 140 劍士/海盜', 36 | mageThiefArcher140: 'Lvl 140 法師/盜賊/弓箭手', 37 | 38 | // Lvl: 200 39 | warrior200: 'Lvl 200 劍士', 40 | archer200: 'Lvl 200 弓箭手', 41 | thiefLab200: 'Lvl 200 盜賊/Lab', 42 | mage200: 'Lvl 200 法師', 43 | pirate200: 'Lvl 200 海盜', 44 | 45 | // Lvl: 250 46 | warrior250: 'Lvl 250 劍士', 47 | archer250: 'Lvl 250 弓箭手', 48 | thief250: 'Lvl 250 盜賊', 49 | mage250: 'Lvl 250 法師', 50 | pirate250: 'Lvl 250 海盜', 51 | xenon250: 'Lvl 250 傑諾', 52 | 53 | /** special jobs */ 54 | enhancedLab200: 'Lvl 200 強化型 Lab', 55 | enhancedLab250: 'Lvl 250 強化型 Lab', 56 | lab250: 'Lvl 250 Lab' 57 | }; 58 | -------------------------------------------------------------------------------- /webpack.config.prod.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const languages = ['GMS', 'KMS', 'JMS', 'TMS', 'CMS']; 5 | 6 | module.exports = { 7 | entry: './src/main.js', 8 | output: { 9 | filename: '[name].[contenthash].js', 10 | path: path.resolve(__dirname, 'dist', 'prod') 11 | }, 12 | mode: 'production', 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader', 19 | options: { 20 | presets: [{'plugins': ['@babel/plugin-proposal-class-properties']}] 21 | } 22 | }, 23 | { 24 | test: /\.css$/, 25 | use: [ 26 | 'style-loader', 27 | 'css-loader' 28 | ] 29 | } 30 | ] 31 | }, 32 | optimization: { 33 | moduleIds: 'deterministic', 34 | runtimeChunk: 'single', 35 | splitChunks: { 36 | cacheGroups: { 37 | vendor: { 38 | test: /[\\/]node_modules[\\/]/, 39 | name: 'vendor', 40 | chunks: 'all', 41 | }, 42 | }, 43 | }, 44 | }, 45 | plugins: [ 46 | new HtmlWebpackPlugin({ 47 | template: 'src/index.html', 48 | inject: false, 49 | languages 50 | }), 51 | ], 52 | }; 53 | -------------------------------------------------------------------------------- /src/locales/cn.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '冒险岛联盟自动排列工具', 3 | instructions: '使用方法:', 4 | instructionsSub1: '1. 点击你想要使用的区块,或者可以使用选取整个区块功能。', 5 | instructionsSub2: '2. 输入你拥有的角色拼图数量。', 6 | instructionsSub3: '3. 必须要确定可使用的区块数量和可填入的区块数量相等,否则自动排列程序将陷入死循环。', 7 | instructionsSub4: "4. 只要点击开始,程序就会自动按照你输入的拼图数量填满区块。你也可以打开实时演算功能来查看过程。", 8 | 9 | spacesToBeFilled: '可填入的区块数量: ', 10 | boardSpacesFilled: '可使用的区块数量: ', 11 | currentCaracterCountFiled : "當当前使用的角色数量: ", 12 | iterations: '尝试次数: ', 13 | time: '消耗时间: ', 14 | 15 | bigClick: '选取整个区块', 16 | liveSolve: '实时演算', 17 | darkMode: '暗黑模式', 18 | 19 | start: '开始', 20 | pause: '暂停', 21 | continue: '继续', 22 | reset: '重置', 23 | clearPieces: '清除所有的拼图', 24 | clearBoard: '清除面板', 25 | failText: '找不到解', 26 | 27 | /** jobs */ 28 | // 等级: 60 29 | lvl60: '等级 60', 30 | 31 | // 等级: 100 32 | lvl100: '等级 100', 33 | 34 | // 等级: 140 35 | warriorPirate140: '等级 140 战士/海盗', 36 | mageThiefArcher140: '等级 140 魔法师/飞侠/尖兵/弓箭手', 37 | 38 | // 等级: 200 39 | warrior200: '等级 200 战士', 40 | archer200: '等级 200 弓箭手', 41 | thiefLab200: '等级 200 飞侠/尖兵', 42 | mage200: '等级 200 魔法师', 43 | pirate200: '等级 200 海盗', 44 | 45 | // 等级: 250 46 | warrior250: '等级 250 战士', 47 | archer250: '等级 250 弓箭手', 48 | thief250: '等级 250 飞侠', 49 | mage250: '等级 250 魔法师', 50 | pirate250: '等级 250 海盗', 51 | xenon250: '等级 250 尖兵', 52 | 53 | /** special jobs */ 54 | enhancedLab200: 'Lvl 200 Enhanced Lab', 55 | enhancedLab250: 'Lvl 250 Enhanced Lab', 56 | lab250: 'Lvl 250 Lab' 57 | } 58 | -------------------------------------------------------------------------------- /src/locales/ja.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'ユニオンマップメーカー', 3 | instructions: '説明:', 4 | 5 | instructionsSub1: '1. ユニオンマップをもっと早く埋めるためには地域選択のボタンを活性化してから地域を選択してください。', 6 | instructionsSub2: '2. ユニオンマップに埋めようとする職業の数を入力してください。', 7 | instructionsSub3: '3. 算定された空間は選択された空間の量と同じでなければならず、同じでなければプログラムは繰り返します。', 8 | instructionsSub4: '4. プログラムがルートを探しているときにその状況を見たいなら、実時間見るボタンを活性化してください。', 9 | 10 | spacesToBeFilled: 'キャラクターの算定された空間: ', 11 | boardSpacesFilled: 'マップの選択された空間: ', 12 | currentCaracterCountFiled : "現在使用されている役割の数: ", 13 | iterations: '繰り返した数: ', 14 | time: '所要時間: ', 15 | 16 | bigClick: '地域選択', 17 | liveSolve: '実時間見る', 18 | darkMode: 'ダークモード', 19 | 20 | start: 'スタート', 21 | pause: 'ポーズ', 22 | continue: 'コンティニュー', 23 | reset: 'リセット', 24 | clearPieces: 'キャラクター初期化', 25 | clearBoard: 'マップ初期化', 26 | failText: 'ルートが見つかりませんでした', 27 | 28 | /** jobs */ 29 | // Lvl: 60 30 | lvl60: 'Lvl 60', 31 | 32 | // Lvl: 100 33 | lvl100: 'Lvl 100', 34 | 35 | // Lvl: 140 36 | warriorPirate140: 'Lvl 140 戦士/海賊', 37 | mageThiefArcher140: 'Lvl 140 魔法使い/盗賊/弓', 38 | 39 | // Lvl: 200 40 | warrior200: 'Lvl 200 戦士', 41 | archer200: 'Lvl 200 弓', 42 | thiefLab200: 'Lvl 200 盗賊/ゼノン', 43 | mage200: 'Lvl 200 魔法使い', 44 | pirate200: 'Lvl 200 海賊', 45 | 46 | // Lvl: 250 47 | warrior250: 'Lvl 250 戦士', 48 | archer250: 'Lvl 250 弓', 49 | thief250: 'Lvl 250 盗賊', 50 | mage250: 'Lvl 250 魔法使い', 51 | pirate250: 'Lvl 250 海賊', 52 | xenon250: 'Lvl 250 ゼノン', 53 | 54 | /** special jobs */ 55 | enhancedLab200: 'Lvl 200 Enhanced Lab', 56 | enhancedLab250: 'Lvl 250 Enhanced Lab', 57 | lab250: 'Lvl 250 Lab' 58 | } 59 | -------------------------------------------------------------------------------- /src/locales/ko.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '유니온 지도 메이커', 3 | instructions: '설명:', 4 | instructionsSub1: '1. 유니온 지도를 더 빨리 채우려면 지역선택 버튼을 활성화 한 후 지역을 선택하세요.', 5 | instructionsSub2: '2. 유니온 지도에 채우려는 직업의 수를 입력하세요.', 6 | instructionsSub3: '3. 산정된 공간은 선택된 공간의 양과 같아야 하며 같지 않다면 프로그램은 계속 반복 실행할 겁니다.', 7 | instructionsSub4: '4. 프로그램이 루트를 찾고 있을 때 해당 과정을 보고 싶다면 실시간 보기를 활성화해 줍니다.', 8 | 9 | spacesToBeFilled: '유니온 캐릭터 산정된 공간: ', 10 | boardSpacesFilled: '유니온 지도 선택된 공간: ', 11 | currentCaracterCountFiled : "현재 사용 중인 문자 수: ", 12 | iterations: '반복횟수: ', 13 | time: '소요시간: ', 14 | 15 | bigClick: '지역선택', 16 | liveSolve: '실시간 보기', 17 | darkMode: '다크모드', 18 | 19 | start: '시작', 20 | pause: 'Pause', 21 | continue: 'Continue', 22 | reset: '리셋', 23 | clearPieces: '캐릭터 초기화', 24 | clearBoard: '유니온 지도 초기화', 25 | failText: '루트를 찾지 못했습니다.', 26 | 27 | /** jobs */ 28 | // Lvl: 60 29 | lvl60: 'Lvl 60', 30 | 31 | // Lvl: 100 32 | lvl100: 'Lvl 100', 33 | 34 | // Lvl: 140 35 | warriorPirate140: 'Lvl 140 전사/해적', 36 | mageThiefArcher140: 'Lvl 140 마법사/도적/궁수/메이플M(S)', 37 | 38 | // Lvl: 200 39 | warrior200: 'Lvl 200 전사', 40 | archer200: 'Lvl 200 궁수/메이플M(SS)', 41 | thiefLab200: 'Lvl 200 도적/제논', 42 | mage200: 'Lvl 200 마법사', 43 | pirate200: 'Lvl 200 해적', 44 | 45 | // Lvl: 250 46 | warrior250: 'Lvl 250 전사', 47 | archer250: 'Lvl 250 궁수', 48 | thief250: 'Lvl 250 도적', 49 | mage250: 'Lvl 250 마법사', 50 | pirate250: 'Lvl 250 해적', 51 | xenon250: 'Lvl 250 제논', 52 | 53 | /** special jobs */ 54 | enhancedLab200: 'Lvl 200 Enhanced Lab', 55 | enhancedLab250: 'Lvl 250 Enhanced Lab', 56 | lab250: 'Lvl 250 Lab' 57 | } 58 | -------------------------------------------------------------------------------- /src/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'Legion Solver', 3 | instructions: 'Instructions:', 4 | instructionsSub1: '1. Click the grid spaces you want to be filled, the region click will help you fill it in faster.', 5 | instructionsSub2: '2. Input the amount of each shape you want to be filled in the board.', 6 | instructionsSub3: '3. The space that the pieces take up should equal the amount of grid spaces you filled, although the program will still try to run otherwise.', 7 | instructionsSub4: "4. When you press Start the program will try to fill the board spaces with the pieces you've chosen, click on Live Solve if you want to see the board filled in real time.", 8 | 9 | spacesToBeFilled: 'Spaces to be Filled: ', 10 | boardSpacesFilled: 'Board Spaces Filled: ', 11 | currentCaracterCountFiled : "Number of Characters: ", 12 | iterations: 'Iterations: ', 13 | time: 'Time: ', 14 | 15 | bigClick: 'Region Click', 16 | liveSolve: 'Live Solve', 17 | darkMode: 'Dark Mode', 18 | 19 | start: 'Start', 20 | pause: 'Pause', 21 | continue: 'Continue', 22 | reset: 'Reset', 23 | clearPieces: 'Clear Pieces', 24 | clearBoard: 'Clear Board', 25 | failText: 'No Solution Found', 26 | 27 | /** jobs */ 28 | // Lvl: 60 29 | lvl60: 'Lvl 60', 30 | 31 | // Lvl: 100 32 | lvl100: 'Lvl 100', 33 | 34 | // Lvl: 140 35 | warriorPirate140: 'Lvl 140 Warrior/Pirate', 36 | mageThiefArcher140: 'Lvl 140 Mage/Thief/Archer', 37 | 38 | // Lvl: 200 39 | warrior200: 'Lvl 200 Warrior', 40 | archer200: 'Lvl 200 Archer', 41 | thiefLab200: 'Lvl 200 Thief/Lab', 42 | mage200: 'Lvl 200 Mage', 43 | pirate200: 'Lvl 200 Pirate', 44 | 45 | // Lvl: 250 46 | warrior250: 'Lvl 250 Warrior', 47 | archer250: 'Lvl 250 Archer', 48 | thief250: 'Lvl 250 Thief/Ride or Die', 49 | mage250: 'Lvl 250 Mage', 50 | pirate250: 'Lvl 250 Pirate/Abyssal', 51 | xenon250: 'Lvl 250 Xenon', 52 | 53 | /** special jobs */ 54 | enhancedLab200: 'Lvl 200 Enhanced Lab', 55 | enhancedLab250: 'Lvl 250 Enhanced Lab', 56 | lab250: 'Lvl 250 Lab' 57 | } 58 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | form { 7 | margin-bottom: 0; 8 | } 9 | 10 | #title { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | margin-top: 0; 15 | } 16 | 17 | #header { 18 | display: flex; 19 | justify-content: flex-end; 20 | } 21 | 22 | #subtitle { 23 | text-align: center; 24 | } 25 | 26 | #legionBoard { 27 | border-spacing: 0; 28 | border-collapse: collapse; 29 | } 30 | 31 | #legionBoard td.legionCell { 32 | width: 25px; 33 | height: 27px; 34 | border-style: solid; 35 | border-width: 1px; 36 | } 37 | 38 | #legion { 39 | display: flex; 40 | flex-direction: column; 41 | } 42 | 43 | #legionFooter { 44 | margin-top: 10px; 45 | } 46 | 47 | #options { 48 | display: flex; 49 | flex-direction: column; 50 | text-align: end; 51 | } 52 | 53 | #checkboxes { 54 | margin-bottom: 10px; 55 | } 56 | 57 | #darkModeLabel { 58 | padding-top: 10px; 59 | } 60 | 61 | #iterationTime { 62 | display: flex; 63 | flex-direction: column; 64 | justify-content: flex-end; 65 | } 66 | 67 | #iterations, #time, #resetButton { 68 | visibility: hidden; 69 | } 70 | 71 | #boardFilled { 72 | margin-bottom: 7px; 73 | } 74 | 75 | #currentCaracterCount { 76 | margin-bottom: 7px; 77 | } 78 | 79 | #middlelabels { 80 | display: flex; 81 | flex-direction: column; 82 | align-items: center; 83 | } 84 | 85 | #pieceForm { 86 | display: flex; 87 | flex-direction: column; 88 | align-items: flex-end; 89 | padding-right: 20px; 90 | } 91 | 92 | #pieceForm td.pieceCell { 93 | width: 7px; 94 | height: 9px; 95 | border-style: solid; 96 | border-width: 0px; 97 | } 98 | 99 | .piece { 100 | display: flex; 101 | flex-direction: row; 102 | margin-bottom: 15px; 103 | justify-content: flex-end; 104 | } 105 | 106 | #resetButton { 107 | margin-top: 5px; 108 | } 109 | 110 | #failText { 111 | text-align: right; 112 | visibility: hidden; 113 | } 114 | 115 | #bigClick, #liveSolve { 116 | margin-left: 20px; 117 | } 118 | 119 | #pieceForm input { 120 | width: 50px; 121 | height: 20px; 122 | margin-left: 10px; 123 | } 124 | 125 | .centerMiddleChild { 126 | display: flex; 127 | } 128 | 129 | .centerMiddleChild > :nth-child(1), .centerMiddleChild > :nth-child(3) { 130 | flex: 1; 131 | } 132 | 133 | #instructions { 134 | margin-left: 30px; 135 | } 136 | 137 | #paragraph { 138 | padding-right: 80px; 139 | } 140 | 141 | #currentPieces { 142 | white-space: nowrap; 143 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% for(var i=0; i < htmlWebpackPlugin.files.js.length; i++) {%> 4 | 5 | <% } %> 6 | 7 | 8 | 9 | 17 |

Legion Solver

18 |
Made by: Xenogent of Bera
19 |
Donate paypal: wellifitisntmoehoward@gmail.com
20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 |
32 | Spaces to be Filled: 0 33 |
34 |
35 | Board Spaces Filled: 0 36 |
37 |
38 | Number of Characters : 0 39 |
40 | 41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 |
54 |
55 |
56 |
57 |
58 | Iterations: 59 |
60 |
61 | Time: 62 |
63 |
64 |
65 |
66 |
67 |
68 |

Instructions:

69 |
70 |

1. Click the grid spaces you want to be filled, the region click will help you fill it in faster.

71 |

2. Input the amount of each shape you want to be filled in the board.

72 |

3. The space that the pieces take up should equal the amount of grid spaces you filled, 73 | although the program will still try to run otherwise.

74 |

4. When you press Start the program will try to fill the board spaces with the pieces you've chosen, 75 | click on Live Solve if you want to see the board filled in real time. 76 |

77 |
78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | import locales from './locales/index.js'; 2 | 3 | let _cr = getCurrentLanguage(); 4 | 5 | function getCurrentLanguage() { 6 | const region = getRegionByBrowserLanguage(); 7 | 8 | // default: GMS 9 | return localStorage.getItem('i18n') || region || 'GMS'; 10 | } 11 | 12 | function getRegionByBrowserLanguage() { 13 | const language = window.navigator.userLanguage || window.navigator.language; 14 | let resultRegion; 15 | 16 | switch (language) { 17 | case 'en': resultRegion = 'GMS'; break; 18 | case 'ko': resultRegion = 'KMS'; break; 19 | case 'ja': resultRegion = 'JMS'; break; 20 | case 'cn': resultRegion = 'CMS'; break; 21 | 22 | default: resultRegion = null; break; 23 | } 24 | 25 | return resultRegion; 26 | } 27 | 28 | function setCurrentLanguage() { 29 | document.getElementById(_cr).selected = true; 30 | } 31 | 32 | function i18n(key) { 33 | return locales[_cr][key]; 34 | } 35 | 36 | document.getElementById('title').textContent = i18n('title'); 37 | getTextNodesIn(document.getElementById('instructions'))[0].textContent = i18n('instructions'); 38 | getTextNodesIn(document.getElementById('paragraph'))[0].textContent = i18n('instructionsSub1'); 39 | getTextNodesIn(document.getElementById('paragraph'))[1].textContent = i18n('instructionsSub2'); 40 | getTextNodesIn(document.getElementById('paragraph'))[2].textContent = i18n('instructionsSub3'); 41 | getTextNodesIn(document.getElementById('paragraph'))[3].textContent = i18n('instructionsSub4'); 42 | getTextNodesIn(document.getElementById('currentPieces'))[0].textContent = i18n('spacesToBeFilled'); 43 | getTextNodesIn(document.getElementById('boardFilled'))[0].textContent = i18n('boardSpacesFilled'); 44 | getTextNodesIn(document.getElementById('currentCaracterCount'))[0].textContent = i18n('currentCaracterCountFiled'); 45 | getTextNodesIn(document.getElementById('iterations'))[0].textContent = i18n('iterations'); 46 | getTextNodesIn(document.getElementById('time'))[0].textContent = i18n('time'); 47 | 48 | document.querySelector('label[for="bigClick"]').textContent = i18n('bigClick'); 49 | document.querySelector('label[for="liveSolve"]').textContent = i18n('liveSolve'); 50 | document.querySelector('label[for="darkMode"]').textContent = i18n('darkMode'); 51 | 52 | document.getElementById('boardButton').textContent = i18n('start'); 53 | document.getElementById('resetButton').textContent = i18n('reset'); 54 | document.getElementById('clearPieces').textContent = i18n('clearPieces'); 55 | document.getElementById('clearBoard').textContent = i18n('clearBoard'); 56 | document.getElementById('failText').textContent = i18n('failText'); 57 | 58 | function getTextNodesIn(node, includeWhitespaceNodes) { 59 | const textNodes = [], whitespace = /^\s*$/; 60 | 61 | function getTextNodes(node) { 62 | if (node.nodeType == 3) { 63 | if (includeWhitespaceNodes || !whitespace.test(node.nodeValue)) { 64 | textNodes.push(node); 65 | } 66 | } else { 67 | for (var i = 0, len = node.childNodes.length; i < len; ++i) { 68 | getTextNodes(node.childNodes[i]); 69 | } 70 | } 71 | } 72 | 73 | getTextNodes(node); 74 | 75 | return textNodes; 76 | } 77 | 78 | document.getElementById("languageSelectBox").addEventListener("change", function () { 79 | localStorage.setItem('i18n', this.value); 80 | location.reload(); 81 | }); 82 | 83 | setCurrentLanguage(); 84 | 85 | export { i18n, getCurrentLanguage }; 86 | -------------------------------------------------------------------------------- /src/modules/piece.js: -------------------------------------------------------------------------------- 1 | import { Point } from './point.js'; 2 | import _ from 'lodash'; 3 | 4 | class Piece { 5 | static curId = 1; 6 | 7 | constructor(shape, amount, id) { 8 | this.shape = shape; 9 | this.amount = amount; 10 | this.id = id; 11 | } 12 | 13 | static createPiece(shape, amount) { 14 | return new Piece(shape, amount, this.curId++); 15 | } 16 | 17 | get cellCount() { 18 | Object.defineProperty(this, "cellCount", { value: 0, writable: true }); 19 | 20 | for (let i = 0; i < this.shape.length; ++i) { 21 | for (let j = 0; j < this.shape[i].length; ++j) { 22 | if (this.shape[i][j] > 0) { 23 | this.cellCount++; 24 | } 25 | } 26 | } 27 | 28 | return this.cellCount; 29 | } 30 | 31 | get pointShape() { 32 | Object.defineProperty(this, "pointShape", { value: []}); 33 | 34 | for (let i = 0; i < this.shape.length; ++i) { 35 | for (let j = 0; j < this.shape[i].length; ++j) { 36 | if (this.shape[i][j] == 1) { 37 | this.pointShape.push(new PiecePoint(j, i, false)); 38 | } else if (this.shape[i][j] == 2) { 39 | this.pointShape.push(new PiecePoint(j, i, true)); 40 | } 41 | } 42 | } 43 | 44 | return this.pointShape; 45 | } 46 | 47 | get offCenter() { 48 | Object.defineProperty(this, "offCenter", { value: 0, writable: true }); 49 | 50 | for (let i = 0; i < this.shape[0].length; i++) { 51 | if (this.shape[0][i] != 0) { 52 | this.offCenter = i; 53 | break; 54 | } 55 | } 56 | 57 | return this.offCenter; 58 | } 59 | 60 | get transformations() { 61 | Object.defineProperty(this, "transformations", { value: [], writable: true}); 62 | 63 | let shape = [...this.shape]; 64 | let newGrid; 65 | 66 | for (let i = 0; i < 2; i++) { 67 | for (let j = 0; j < 4; j++) { 68 | newGrid = new Array(shape[0].length).fill(0).map(() => new Array(shape.length).fill(0)); 69 | for (let k = 0; k < shape.length; k++) { 70 | for (let l = 0; l < shape[0].length; l++) { 71 | if (shape[k][l] != 0) { 72 | newGrid[shape[0].length-l-1][k] = shape[k][l]; 73 | } 74 | } 75 | } 76 | shape = newGrid; 77 | this.transformations.push(new Piece(shape, this.amount, this.id)); 78 | } 79 | newGrid = new Array(shape.length).fill(0).map(() => new Array(shape[0].length).fill(0)); 80 | for (let k = 0; k < shape.length; k++) { 81 | for (let l = 0; l < shape[0].length; l++) { 82 | if (shape[k][l] != 0) { 83 | newGrid[shape.length-k-1][l] = shape[k][l]; 84 | } 85 | } 86 | } 87 | shape = newGrid; 88 | } 89 | 90 | this.transformations = _.unionWith(this.transformations, _.isEqual); 91 | return this.transformations; 92 | } 93 | 94 | get pointTransformations() { 95 | Object.defineProperty(this, "pointTransformations", { value: []}); 96 | for (let piece of this.transformations) { 97 | this.pointTransformations.push(piece.pointShape); 98 | } 99 | 100 | return this.pointTransformations; 101 | } 102 | 103 | get restrictedTransformations() { 104 | Object.defineProperty(this, "restrictedTransformations", { value: []}); 105 | for (let piece of this.transformations) { 106 | if (!piece.shape[0][1 + piece.offCenter] || piece.shape[0][1 + piece.offCenter] == 0) { 107 | this.restrictedTransformations.push(piece); 108 | } 109 | } 110 | return this.restrictedTransformations; 111 | } 112 | 113 | get restrictedPointTransformations() { 114 | Object.defineProperty(this, "restrictedPointTransformations", { value: []}); 115 | for (let piece of this.restrictedTransformations) { 116 | this.restrictedPointTransformations.push(piece.pointShape); 117 | 118 | } 119 | return this.restrictedPointTransformations; 120 | } 121 | } 122 | 123 | class PiecePoint extends Point { 124 | constructor(x, y, isMiddle) { 125 | super(x, y); 126 | this.isMiddle = isMiddle; 127 | } 128 | } 129 | 130 | export { Piece, PiecePoint }; -------------------------------------------------------------------------------- /src/pieces.js: -------------------------------------------------------------------------------- 1 | import { Piece } from './modules/piece.js'; 2 | import { sumBy } from 'lodash'; 3 | import { i18n, getCurrentLanguage } from './i18n.js'; 4 | 5 | // TODO: Remove extra 2s. 6 | 7 | const defaultPieces = [ 8 | // Lvl 60 9 | [ 10 | [2] 11 | ], 12 | 13 | // Lvl 100 14 | [ 15 | [2, 2] 16 | ], 17 | 18 | // Lvl 140 Warrior/Pirate 19 | [ 20 | [1, 0], 21 | [2, 1] 22 | ], 23 | 24 | // Lvl 140 Mage/Thief/Archer 25 | [ 26 | [1, 2, 1] 27 | ], 28 | 29 | // Lvl 200 Warrior 30 | [ 31 | [2, 2], 32 | [2, 2] 33 | ], 34 | 35 | // Lvl 200 Archer 36 | [ 37 | [1, 2, 2, 1] 38 | ], 39 | 40 | // Lvl 200 Thief/Lab 41 | [ 42 | [1, 0, 0], 43 | [1, 2, 1] 44 | ], 45 | 46 | // Lvl 200 Mage 47 | [ 48 | [0, 1, 0], 49 | [1, 2, 1] 50 | ], 51 | 52 | // Lvl 200 Pirate 53 | [ 54 | [1, 2, 0], 55 | [0, 2, 1] 56 | ], 57 | 58 | // Lvl 250 Warrior 59 | [ 60 | [1, 1, 2], 61 | [0, 1, 1] 62 | ], 63 | 64 | // Lvl 250 Archer 65 | [ 66 | [1, 1, 2, 1, 1], 67 | ], 68 | 69 | // Lvl 250 Thief/Ride or Die 70 | [ 71 | [0, 0, 1], 72 | [1, 2, 1], 73 | [0, 0, 1] 74 | ], 75 | 76 | // Lvl 250 Mage 77 | [ 78 | [0, 1, 0], 79 | [1, 2, 1], 80 | [0, 1, 0] 81 | ], 82 | 83 | // Lvl 250 Pirate/Abyssal 84 | [ 85 | [1, 2, 0, 0], 86 | [0, 1, 1, 1] 87 | ], 88 | 89 | // Lvl 250 Xenon 90 | [ 91 | [1, 1, 0], 92 | [0, 2, 0], 93 | [0, 1, 1] 94 | ], 95 | ]; 96 | 97 | const gmsPieces = [ 98 | // Lvl 200 Enhanced Lab 99 | [ 100 | [1, 0, 0, 0], 101 | [0, 1, 2, 1] 102 | ], 103 | 104 | // Lvl 250 Enhanced Lab 105 | [ 106 | [1, 0, 0, 0, 1], 107 | [0, 1, 2, 1, 0] 108 | ], 109 | 110 | // Lvl 250 Lab 111 | [ 112 | [1, 0, 1], 113 | [1, 2, 1] 114 | ], 115 | ]; 116 | 117 | const pieces = [] 118 | for (let piece of defaultPieces){ 119 | pieces.push(Piece.createPiece(piece, 0)); 120 | } 121 | 122 | function hasLabPieces() { 123 | return !!['GMS', 'TMS'].find(lang => lang === getCurrentLanguage()); 124 | } 125 | 126 | if (hasLabPieces()) { 127 | for (let piece of gmsPieces){ 128 | pieces.push(Piece.createPiece(piece, 0)); 129 | } 130 | } 131 | 132 | let pieceColours = new Map(); 133 | pieceColours.set(-1, 'white'); 134 | pieceColours.set(0, 'grey'); 135 | for (let i = 0; i < 2; i++) { 136 | pieceColours.set(1 + i * 18, 'lightpink'); 137 | pieceColours.set(2 + i * 18, 'lightcoral'); 138 | pieceColours.set(3 + i * 18, 'indianred'); 139 | pieceColours.set(4 + i * 18, 'darkseagreen'); 140 | pieceColours.set(5 + i * 18, 'firebrick'); 141 | pieceColours.set(6 + i * 18, 'mediumseagreen'); 142 | pieceColours.set(7 + i * 18, 'purple'); 143 | pieceColours.set(8 + i * 18, 'dodgerblue'); 144 | pieceColours.set(9 + i * 18, 'lightsteelblue'); 145 | pieceColours.set(10 + i * 18, 'maroon'); 146 | pieceColours.set(11 + i * 18, 'green'); 147 | pieceColours.set(12 + i * 18, 'indigo'); 148 | pieceColours.set(13 + i * 18, 'blue'); 149 | pieceColours.set(14 + i * 18, 'cadetblue'); 150 | pieceColours.set(15 + i * 18, 'mediumpurple'); 151 | pieceColours.set(16 + i * 18, 'aquamarine'); 152 | pieceColours.set(17 + i * 18, 'aquamarine'); 153 | pieceColours.set(18 + i * 18, 'aquamarine'); 154 | } 155 | 156 | for (let i = 0; i < pieces.length; i++) { 157 | let row = ''.repeat(pieces[i].shape[0].length); 158 | let grid = `${row}`.repeat(pieces[i].shape.length); 159 | document.querySelector('#pieceForm form').innerHTML += `
160 |
161 | 166 | 167 |
`; 168 | 169 | document.getElementById(`pieceDisplay${i+1}`).style.borderCollapse = 'collapse'; 170 | document.getElementById(`pieceDisplay${i+1}`).style.borderSpacing = '0'; 171 | document.getElementById(`pieceDescription${i+1}`).style.paddingRight = '15px'; 172 | 173 | for (let j = 0; j < pieces[i].shape.length; j++) { 174 | for (let k = 0; k < pieces[i].shape[j].length; k++) { 175 | if (pieces[i].shape[j][k] != 0) { 176 | document.getElementById(`pieceDisplay${i+1}`) 177 | .getElementsByTagName("tr")[j] 178 | .getElementsByTagName("td")[k].style.background = pieceColours.get(i+1); 179 | } 180 | } 181 | } 182 | } 183 | 184 | document.getElementById('pieceDescription1').textContent = i18n('lvl60'); 185 | document.getElementById('pieceDescription2').textContent = i18n('lvl100'); 186 | document.getElementById('pieceDescription3').textContent = i18n('warriorPirate140'); 187 | document.getElementById('pieceDescription4').textContent = i18n('mageThiefArcher140'); 188 | document.getElementById('pieceDescription5').textContent = i18n('warrior200'); 189 | document.getElementById('pieceDescription6').textContent = i18n('archer200'); 190 | document.getElementById('pieceDescription7').textContent = i18n('thiefLab200'); 191 | document.getElementById('pieceDescription8').textContent = i18n('mage200'); 192 | document.getElementById('pieceDescription9').textContent = i18n('pirate200'); 193 | document.getElementById('pieceDescription10').textContent = i18n('warrior250'); 194 | document.getElementById('pieceDescription11').textContent = i18n('archer250'); 195 | document.getElementById('pieceDescription12').textContent = i18n('thief250'); 196 | document.getElementById('pieceDescription13').textContent = i18n('mage250'); 197 | document.getElementById('pieceDescription14').textContent = i18n('pirate250'); 198 | document.getElementById('pieceDescription15').textContent = i18n('xenon250'); 199 | 200 | if (hasLabPieces()) { 201 | document.getElementById('pieceDescription16').textContent = i18n('enhancedLab200'); 202 | document.getElementById('pieceDescription17').textContent = i18n('enhancedLab250'); 203 | document.getElementById('pieceDescription18').textContent = i18n('lab250'); 204 | } 205 | 206 | let currentPieces = 0; 207 | let currentUseCaracterCount = 0; 208 | if (localStorage.getItem("currentPieces")) { 209 | currentPieces = JSON.parse(localStorage.getItem("currentPieces")); 210 | document.getElementById('currentPiecesValue').innerText = `${currentPieces}`; 211 | } 212 | 213 | let pieceAmounts = JSON.parse(localStorage.getItem("pieceAmounts")) 214 | if (pieceAmounts) { 215 | for (let i = 0; i < pieces.length; i++) { 216 | document.getElementById(`piece${i+1}`).value = pieceAmounts[i] || 0; 217 | } 218 | 219 | updateCurrentPieces(); 220 | } 221 | 222 | document.getElementById('pieceForm').addEventListener("input", updateCurrentPieces); 223 | 224 | function updateCurrentPieces() { 225 | for (let piece of pieces) { 226 | piece.amount = parseInt(document.getElementById(`piece${piece.id}`).value) || 0; 227 | } 228 | 229 | currentPieces = sumBy(pieces, piece => piece.cellCount * piece.amount); 230 | currentUseCaracterCount = sumBy(pieces, (piece) => piece.amount); 231 | 232 | localStorage.setItem("pieceAmounts", JSON.stringify(pieces.map(piece => piece.amount))); 233 | localStorage.setItem("currentPieces", JSON.stringify(currentPieces)); 234 | 235 | document.getElementById('currentPiecesValue').innerText = `${currentPieces}`; 236 | document.getElementById("currentCaracterCountValue").innerText = `${currentUseCaracterCount}`; 237 | } 238 | 239 | document.getElementById("clearPieces").addEventListener("click", clearPieces); 240 | 241 | function clearPieces() { 242 | for (let i = 0; i < pieces.length; i++) { 243 | document.getElementById(`piece${i+1}`).value = 0; 244 | } 245 | 246 | updateCurrentPieces(); 247 | } 248 | 249 | export { pieceColours, pieces }; -------------------------------------------------------------------------------- /src/modules/legion_solver.js: -------------------------------------------------------------------------------- 1 | import { Point } from './point.js'; 2 | import { Piece } from './piece.js'; 3 | 4 | class LegionSolver { 5 | pausePromise; 6 | pauseResolve; 7 | iterations 8 | directionFree; 9 | success; 10 | shouldStop; 11 | 12 | constructor(board, pieces, onBoardUpdated) { 13 | this.board = board; 14 | this.pieces = pieces; 15 | this.onBoardUpdated = onBoardUpdated; 16 | this.iterations = 0; 17 | this.pieceLength = pieces.length; 18 | this.valid = true; 19 | this.pieceNumber = 0; 20 | this.transformationNumber = 0; 21 | this.restrictedPieceNumber = 0; 22 | this.restrictedTransformationNumber = 0; 23 | this.time = new Date().getTime(); 24 | this.history = []; 25 | 26 | this.middle = []; 27 | for (let i = this.board.length / 2 - 1; i < this.board.length / 2 + 1; i++) { 28 | for (let j = this.board[0].length / 2 - 1; j < this.board[0].length / 2 + 1; j++) { 29 | if (this.board[i][j] != -1) { 30 | this.middle.push(new Point(j, i)); 31 | } 32 | } 33 | } 34 | 35 | this.emptySpots = []; 36 | for (let i = 0; i < this.board.length; i++) { 37 | for (let j = 0; j < this.board[0].length; j++) { 38 | if (this.board[i][j] == 0) { 39 | this.emptySpots.push(new Point(j, i)); 40 | } 41 | } 42 | } 43 | 44 | this.restrictedSpots = []; 45 | for (let i = 0; i < this.board.length; i++) { 46 | for (let j = 0; j < this.board[0].length; j++) { 47 | this.searchSurroundings(j, i); 48 | } 49 | } 50 | 51 | this.longSpaces = []; 52 | for (let i = 0; i < this.board.length; i++) { 53 | for (let j = 0; j < this.board[0].length; j++) { 54 | if (this.checkLongSpace(j, i) == "horizontal") { 55 | this.longSpaces.push(new Point(j, i)); 56 | } 57 | if (this.checkLongSpace(j, i) == "vertical") { 58 | this.longSpaces.push(new Point(j, i)); 59 | } 60 | } 61 | } 62 | this.firstAlgorithm = !!this.longSpaces.length; 63 | } 64 | 65 | async solve() { 66 | this.pieces.sort((a, b) => b.amount * b.cellCount - a.amount * a.cellCount); 67 | this.pieces.push(new Piece([[]], 0, -1)); 68 | this.restrictedSpots.sort((a, b) => b.spotsFilled - a.spotsFilled); 69 | this.success = await this.solveInternal(); 70 | return this.success; 71 | } 72 | 73 | async solveInternal(batchSize=30000) { 74 | let stack = []; 75 | let spotsMoved; 76 | let piece; 77 | let point; 78 | let position = 0; 79 | 80 | while (this.pieces[0].amount > 0 || !this.valid) { 81 | if (this.shouldStop) { 82 | return; 83 | } 84 | if (this.valid && this.restrictedSpots.length != 0 && this.pieces[this.restrictedPieceNumber].amount && this.directionFree != 5 && !this.firstAlgorithm) { 85 | if (this.restrictedPieceNumber != this.pieceLength) { 86 | point = this.restrictedSpots[0]; 87 | piece = this.pieces[this.restrictedPieceNumber].restrictedTransformations[this.restrictedTransformationNumber]; 88 | this.determineDirectionFree(point); 89 | if (this.isPlaceable(point, piece)) { 90 | stack.push([0, 0, this.takeFromList(this.restrictedPieceNumber), [...this.restrictedSpots], 91 | point, this.restrictedPieceNumber, this.restrictedTransformationNumber, this.directionFree, [], 0, this.valid]); 92 | this.restrictedSpots.splice(0, 1); 93 | this.placePiece(point, piece); 94 | this.isValid(); 95 | this.restrictedPieceNumber = 0; 96 | this.restrictedTransformationNumber = 0; 97 | } else { 98 | this.changeIndex(true); 99 | } 100 | } 101 | } else if (this.valid && this.pieces[this.pieceNumber].amount && (this.firstAlgorithm || this.restrictedSpots.length == 0) && this.directionFree != 5){ 102 | this.directionFree = 0; 103 | if (!this.firstAlgorithm) { 104 | position = 0; 105 | while (position < this.emptySpots.length && this.board[this.emptySpots[position].y][this.emptySpots[position].x] != 0) { 106 | position++; 107 | } 108 | } else { 109 | 110 | } 111 | if (position == this.emptySpots.length) { 112 | return true; 113 | } 114 | point = this.emptySpots[position]; 115 | piece = this.pieces[this.pieceNumber].transformations[this.transformationNumber]; 116 | if (this.isPlaceable(point, piece)) { 117 | let filler = []; 118 | for (let i = 0; i < this.longSpaces.length; i++) { 119 | filler.push(this.longSpaces[i]); 120 | } 121 | stack.push([this.pieceNumber, this.transformationNumber, this.takeFromList(this.pieceNumber), [...this.restrictedSpots], 122 | point, 0, 0, 0, filler, position, this.valid]); 123 | this.placePiece(point, piece); 124 | this.isValid(); 125 | 126 | if (this.firstAlgorithm) { 127 | while (position < this.emptySpots.length && this.board[this.emptySpots[position].y][this.emptySpots[position].x] != 0) { 128 | position++; 129 | } 130 | if (position == this.emptySpots.length) { 131 | return true; 132 | } 133 | } 134 | 135 | this.pieceNumber = 0; 136 | this.transformationNumber = 0; 137 | } else { 138 | this.changeIndex(false); 139 | } 140 | } else { 141 | if (stack.length == 0) { 142 | return false; 143 | } 144 | if (!this.valid) { 145 | this.valid = true; 146 | } 147 | 148 | [this.pieceNumber, this.transformationNumber, spotsMoved, this.restrictedSpots, 149 | point, this.restrictedPieceNumber, this.restrictedTransformationNumber, this.directionFree, this.longSpaces, position, this.valid] = stack.pop(); 150 | if (this.directionFree == 0) { 151 | this.returnToList(this.pieceNumber, spotsMoved); 152 | this.takeBackPiece(point, this.pieces[this.pieceNumber].transformations[this.transformationNumber]) 153 | } else { 154 | this.returnToList(this.restrictedPieceNumber, spotsMoved); 155 | this.takeBackPiece(point, this.pieces[this.restrictedPieceNumber].restrictedTransformations[this.restrictedTransformationNumber]) 156 | } 157 | this.firstAlgorithm = !(this.longSpaces.length == 0); 158 | if (!this.firstAlgorithm) { 159 | this.changeIndex(!this.restrictedSpots.length == 0) 160 | } else { 161 | this.changeIndex(false); 162 | } 163 | 164 | } 165 | 166 | this.iterations++; 167 | if (this.iterations % batchSize == 0) { 168 | this.onBoardUpdated(); 169 | await new Promise(resolve => setTimeout(resolve, 0)); 170 | await this.pausePromise; 171 | } 172 | } 173 | 174 | return true; 175 | } 176 | 177 | takeFromList(placement) { 178 | this.pieces[placement].amount--; 179 | let fill = this.pieces[placement]; 180 | let index = placement + 1; 181 | while (fill.amount * fill.cellCount < this.pieces[index].amount * this.pieces[index].cellCount) 182 | index++; 183 | this.pieces[placement] = this.pieces[index - 1]; 184 | this.pieces[index - 1] = fill; 185 | return index - 1 - placement; 186 | } 187 | 188 | returnToList(placement, spotsMoved) { 189 | let fill = this.pieces[placement]; 190 | this.pieces[placement] = this.pieces[placement + spotsMoved]; 191 | this.pieces[placement + spotsMoved] = fill; 192 | this.pieces[placement].amount++; 193 | } 194 | 195 | isValid() { 196 | if (this.middle.length == 0) 197 | return true; 198 | 199 | let normalPieces = 0; 200 | for (let point of this.middle) { 201 | if (this.board[point.y][point.x] > 0 && this.board[point.y][point.x] <= this.pieceLength) { 202 | normalPieces++; 203 | } 204 | } 205 | 206 | this.valid = normalPieces != this.middle.length; 207 | } 208 | 209 | isPlaceable(position, piece) { 210 | if (!piece) { 211 | return false; 212 | } 213 | for (let point of piece.pointShape) { 214 | let x; 215 | let y; 216 | [x, y] = this.determinePoint(position, piece, point); 217 | if ( 218 | y >= this.board.length 219 | || y < 0 220 | || x >= this.board[0].length 221 | || x < 0 222 | || this.board[y][x] != 0) { 223 | return false; 224 | } 225 | } 226 | 227 | return true; 228 | } 229 | 230 | 231 | placePiece(position, piece) { 232 | let realPoints = [] 233 | this.history[this.history.length] = []; 234 | for (let point of piece.pointShape) { 235 | let x; 236 | let y; 237 | [x, y] = this.determinePoint(position, piece, point); 238 | if (!point.isMiddle) { 239 | this.board[y][x] = piece.id; 240 | } else { 241 | this.board[y][x] = piece.id + 18; 242 | } 243 | realPoints.push(new Point(x, y)) 244 | this.history[this.history.length - 1].push(new Point(x, y)) 245 | for (let i = 0; i < this.restrictedSpots.length; i++) { 246 | if (this.restrictedSpots[i].x == x && this.restrictedSpots[i].y == y) { 247 | this.restrictedSpots.splice(i, 1) 248 | i--; 249 | } 250 | } 251 | for (let i = 0; i < this.longSpaces.length; i++) { 252 | if (this.longSpaces[i].x == x && this.longSpaces[i].y == y) { 253 | this.longSpaces.splice(i, 1) 254 | i--; 255 | } 256 | } 257 | if (this.longSpaces.length == 0) { 258 | this.firstAlgorithm = false; 259 | } 260 | } 261 | for (let point of realPoints) { 262 | this.searchSurroundings(point.x, point.y + 1) 263 | this.searchSurroundings(point.x, point.y - 1) 264 | this.searchSurroundings(point.x + 1, point.y) 265 | this.searchSurroundings(point.x - 1, point.y) 266 | } 267 | 268 | let spliceElements = [] 269 | for (let i = 0; i < this.restrictedSpots.length - 1; i++) { 270 | for (let j = i + 1; j < this.restrictedSpots.length; j++) { 271 | if (this.restrictedSpots[i].x == this.restrictedSpots[j].x && this.restrictedSpots[i].y == this.restrictedSpots[j].y) { 272 | spliceElements.push(i); 273 | } 274 | } 275 | } 276 | for (let i = spliceElements.length - 1; i >= 0; i--) { 277 | this.restrictedSpots.splice(spliceElements[i], 1); 278 | } 279 | this.restrictedSpots.sort((a, b) => b.spotsFilled - a.spotsFilled) 280 | } 281 | 282 | takeBackPiece(position, piece) { 283 | this.history.pop(); 284 | for (let point of piece.pointShape) { 285 | let x; 286 | let y; 287 | [x, y] = this.determinePoint(position, piece, point); 288 | this.board[y][x] = 0; 289 | } 290 | } 291 | 292 | searchSurroundings(x, y) { 293 | let restrictedSpaces = 0; 294 | if (this.board[y] && this.board[y][x] == 0) { 295 | if (this.board[y + 1] && this.board[y + 1][x] == 0) { 296 | restrictedSpaces++; 297 | } 298 | if (this.board[y - 1] && this.board[y - 1][x] == 0) { 299 | restrictedSpaces++; 300 | } 301 | if (this.board[y] && this.board[y][x + 1] == 0) { 302 | restrictedSpaces++; 303 | } 304 | if (this.board[y] && this.board[y][x - 1] == 0) { 305 | restrictedSpaces++; 306 | } 307 | if (restrictedSpaces <= 1) { 308 | this.restrictedSpots.push(new RestrictedPoint(x, y, 4 - restrictedSpaces)); 309 | } 310 | } 311 | } 312 | 313 | checkLongSpace(x, y) { 314 | if (this.board[y + 1] && this.board[y + 1][x] == 0 315 | && this.board[y - 1] && this.board[y - 1][x] == 0 316 | && this.board[y] && this.board[y][x + 1] != 0 317 | && this.board[y] && this.board[y][x - 1] != 0) { 318 | return "vertical"; 319 | } 320 | if (this.board[y + 1] && this.board[y + 1][x] != 0 321 | && this.board[y - 1] && this.board[y - 1][x] != 0 322 | && this.board[y] && this.board[y][x + 1] == 0 323 | && this.board[y] && this.board[y][x - 1] == 0) { 324 | return "horizontal"; 325 | } 326 | } 327 | 328 | changeIndex(restricted) { 329 | if (restricted) { 330 | if (this.restrictedTransformationNumber < this.pieces[this.restrictedPieceNumber].restrictedTransformations.length - 1) { 331 | this.restrictedTransformationNumber++; 332 | } else { 333 | this.restrictedPieceNumber++; 334 | this.restrictedTransformationNumber = 0; 335 | } 336 | } else { 337 | if (this.transformationNumber < this.pieces[this.pieceNumber].transformations.length - 1) { 338 | this.transformationNumber++; 339 | } else { 340 | this.pieceNumber++; 341 | this.transformationNumber = 0; 342 | } 343 | } 344 | } 345 | 346 | determineDirectionFree(point) { 347 | if (this.board[point.y - 1] && this.board[point.y - 1][point.x] == 0) { 348 | this.directionFree = 1; 349 | } else if (this.board[point.y] && this.board[point.y][point.x + 1] == 0) { 350 | this.directionFree = 2; 351 | } else if (this.board[point.y + 1] && this.board[point.y + 1][point.x] == 0) { 352 | this.directionFree = 3; 353 | } else if (this.board[point.y] && this.board[point.y][point.x - 1] == 0) { 354 | this.directionFree = 4; 355 | } else { 356 | this.directionFree = 5; 357 | } 358 | } 359 | 360 | determinePoint(position, piece, point) { 361 | let x; 362 | let y; 363 | if (this.directionFree == 0 || this.directionFree == 3 || this.directionFree == 5) { 364 | x = position.x + point.x - piece.offCenter; 365 | y = position.y + point.y; 366 | } else if (this.directionFree == 1) { 367 | x = position.x - point.x + piece.offCenter; 368 | y = position.y - point.y; 369 | } else if (this.directionFree == 2) { 370 | x = position.x + point.y; 371 | y = position.y + point.x - piece.offCenter; 372 | } else { 373 | x = position.x - point.y; 374 | y = position.y - point.x + piece.offCenter; 375 | } 376 | return [x, y]; 377 | } 378 | 379 | pause() { 380 | this.time -= new Date().getTime(); 381 | if (this.iterations != 0) { 382 | document.getElementById("iterations").style.visibility = 'visible'; 383 | document.getElementById("iterationsValue").innerText = `${this.iterations}`; 384 | 385 | document.getElementById("time").style.visibility = 'visible'; 386 | document.getElementById("timeValue").innerText = `${-this.time}ms`; 387 | } 388 | this.pausePromise = new Promise(resolve => this.pauseResolve = resolve); 389 | } 390 | 391 | continue() { 392 | this.time += new Date().getTime(); 393 | document.getElementById("iterations").style.visibility = 'hidden'; 394 | document.getElementById("time").style.visibility = 'hidden'; 395 | this.pauseResolve(); 396 | this.pausePromise = null; 397 | } 398 | 399 | stop() { 400 | this.shouldStop = true; 401 | } 402 | } 403 | 404 | class RestrictedPoint extends Point { 405 | constructor(x, y, spotsFilled) { 406 | super(x, y) 407 | this.spotsFilled = spotsFilled; 408 | } 409 | } 410 | 411 | export { LegionSolver }; -------------------------------------------------------------------------------- /src/board.js: -------------------------------------------------------------------------------- 1 | import { Point } from './modules/point.js'; 2 | import { LegionSolver } from './modules/legion_solver.js'; 3 | import { pieceColours, pieces } from './pieces.js'; 4 | import { i18n } from './i18n.js'; 5 | 6 | let board = JSON.parse(localStorage.getItem("legionBoard")); 7 | if (!board) { 8 | board = []; 9 | for (let i = 0; i < 20; i++) { 10 | board[i] = []; 11 | for (let j = 0; j < 22; j++) { 12 | board[i][j] = -1; 13 | } 14 | } 15 | } 16 | let legionSolvers = []; 17 | let pieceHistory = []; 18 | 19 | const states = { 20 | START: 'start', 21 | RUNNING: 'running', 22 | PAUSED: 'paused', 23 | COMPLETED: 'completed', 24 | } 25 | let state = states.START; 26 | 27 | const legionGroups = []; 28 | for (let i = 0; i < 16; i++) { 29 | legionGroups[i] = []; 30 | } 31 | 32 | document.querySelector('#legionBoard tbody').innerHTML = 33 | board.map(row => `${row.map(_ => ``).join('')}`).join(''); 34 | 35 | drawBoard(); 36 | setLegionGroups(); 37 | 38 | let boardFilled = 0; 39 | if (localStorage.getItem("boardFilled")) { 40 | boardFilled = JSON.parse(localStorage.getItem("boardFilled")); 41 | document.getElementById('boardFilledValue').innerText = `${boardFilled}`; 42 | } 43 | 44 | let isBigClick = false; 45 | if (localStorage.getItem("isBigClick")) { 46 | document.getElementById("bigClick").checked = JSON.parse(localStorage.getItem("isBigClick")); 47 | if (JSON.parse(localStorage.getItem("isBigClick"))) { 48 | activateBigClick(); 49 | } 50 | } 51 | 52 | let isLiveSolve = false; 53 | if (localStorage.getItem("isLiveSolve")) { 54 | document.getElementById("liveSolve").checked = JSON.parse(localStorage.getItem("isLiveSolve")); 55 | if (JSON.parse(localStorage.getItem("isLiveSolve"))) { 56 | activateLiveSolve(); 57 | } 58 | } 59 | 60 | document.getElementById("bigClick").addEventListener("click", activateBigClick); 61 | document.getElementById("liveSolve").addEventListener("click", activateLiveSolve); 62 | document.getElementById("clearBoard").addEventListener("click", clearBoard); 63 | document.getElementById("boardButton").addEventListener("click", handleButton); 64 | document.getElementById("resetButton").addEventListener("click", reset); 65 | document.getElementById("darkMode").addEventListener("click", activateDarkMode); 66 | 67 | let dragging = false; 68 | let dragValue; 69 | for (let i = 0; i < board.length; i++) { 70 | for (let j = 0; j < board[0].length; j++) { 71 | let grid = getLegionCell(i, j) 72 | 73 | grid.addEventListener("mousedown", () => { 74 | dragValue = board[i][j] == 0 ? -1 : 0; 75 | setBoard(i, j, dragValue); 76 | dragging = true; 77 | }); 78 | grid.addEventListener("mouseover", () => { 79 | if (dragging) { 80 | setBoard(i, j, dragValue); 81 | } else { 82 | hoverOverBoard(i, j); 83 | } 84 | }); 85 | grid.addEventListener("mouseout", () => { 86 | if (!dragging) { 87 | hoverOffBoard(i, j) ; 88 | } 89 | }); 90 | } 91 | } 92 | document.documentElement.addEventListener("mouseup", () => { dragging = false }); 93 | document.getElementById("legion").addEventListener("dragstart", (evt) => evt.preventDefault()); 94 | 95 | function setLegionGroups() { 96 | for (let i = 0; i < board.length / 4; i++) { 97 | for (let j = i; j < board.length / 2; j++) { 98 | legionGroups[0].push(new Point(j, i)); 99 | legionGroups[1].push(new Point(i, j + 1)) 100 | legionGroups[2].push(new Point(i, board[0].length - 2 - j)) 101 | legionGroups[3].push(new Point(j, board[0].length - 1 - i)) 102 | legionGroups[4].push(new Point(board.length - 1 - j, board[0].length - 1 - i)) 103 | legionGroups[5].push(new Point(board.length - 1 - i, board[0].length - 2 - j)) 104 | legionGroups[6].push(new Point(board.length - 1 - i, j + 1)) 105 | legionGroups[7].push(new Point(board.length - 1 - j, i)) 106 | } 107 | } 108 | for (let i = board.length / 4; i < board.length / 2; i++) { 109 | for (let j = i; j < board.length / 2; j++) { 110 | legionGroups[8].push(new Point(j, i)); 111 | legionGroups[9].push(new Point(i, j + 1)); 112 | legionGroups[10].push(new Point(3 * board.length / 4 - 1 - j, board.length / 4 + 1 + i)); 113 | legionGroups[11].push(new Point(j, board[0].length - 1 - i)); 114 | legionGroups[12].push(new Point(board.length - 1 - j, board[0].length - 1 - i)); 115 | legionGroups[13].push(new Point(j + board.length / 4, i + board.length / 4 + 1)); 116 | legionGroups[14].push(new Point(j + board.length / 4, 3 * board.length / 4 - i)); 117 | legionGroups[15].push(new Point(board.length - j - 1, i)); 118 | } 119 | } 120 | } 121 | 122 | function setLegionBorders() { 123 | for (let i = 0; i < board.length; i++) { 124 | for (let j = 0; j < board[0].length; j++) { 125 | getLegionCell(i, j).style.borderWidth = '1px'; 126 | } 127 | } 128 | for (let i = 0; i < board[0].length / 2; i++) { 129 | getLegionCell(i, i).style.borderTopWidth = '3px'; 130 | getLegionCell(i, i).style.borderRightWidth = '3px'; 131 | getLegionCell(board.length - i - 1, i).style.borderBottomWidth = '3px'; 132 | getLegionCell(board.length - i - 1, i).style.borderRightWidth = '3px'; 133 | getLegionCell(i, board[0].length - i - 1).style.borderTopWidth = '3px'; 134 | getLegionCell(i, board[0].length - i - 1).style.borderLeftWidth = '3px'; 135 | getLegionCell(board.length - i - 1, board[0].length - i - 1).style.borderBottomWidth = '3px'; 136 | getLegionCell(board.length - i - 1, board[0].length - i - 1).style.borderLeftWidth = '3px'; 137 | } 138 | for (let i = 0; i < board.length; i++) { 139 | getLegionCell(i, 0).style.borderLeftWidth = '3px'; 140 | getLegionCell(i, board[0].length / 2).style.borderLeftWidth = '3px'; 141 | getLegionCell(i, board[0].length - 1).style.borderRightWidth = '3px'; 142 | } 143 | for (let i = 0; i < board[0].length; i++) { 144 | getLegionCell(0, i).style.borderTopWidth = '3px'; 145 | getLegionCell(board.length / 2, i).style.borderTopWidth = '3px'; 146 | getLegionCell(board.length - 1, i).style.borderBottomWidth = '3px'; 147 | } 148 | for (let i = board.length / 4; i < 3 * board.length / 4; i++) { 149 | getLegionCell(i, Math.floor(board[0].length / 4)).style.borderLeftWidth = '3px'; 150 | getLegionCell(i, Math.floor(3 * board[0].length / 4)).style.borderRightWidth = '3px'; 151 | } 152 | for (let i = Math.ceil(board[0].length / 4); i < Math.floor(3 * board[0].length / 4); i++) { 153 | getLegionCell(board.length / 4, i).style.borderTopWidth = '3px'; 154 | getLegionCell(3 * board.length / 4, i).style.borderTopWidth = '3px'; 155 | } 156 | } 157 | 158 | let isDarkMode = false; 159 | if (localStorage.getItem("isDarkMode")) { 160 | document.getElementById("darkMode").checked = JSON.parse(localStorage.getItem("isDarkMode")); 161 | if (JSON.parse(localStorage.getItem("isDarkMode"))) { 162 | activateDarkMode(); 163 | } 164 | } 165 | 166 | 167 | function findGroupNumber(i, j) { 168 | for (let k = 0; k < legionGroups.length; k++) { 169 | for (let point of legionGroups[k]) { 170 | if (point.x == i && point.y == j) { 171 | return k; 172 | } 173 | } 174 | } 175 | } 176 | 177 | function getLegionCell(i, j) { 178 | return document.getElementById("legionBoard") 179 | .getElementsByTagName("tr")[i] 180 | .getElementsByTagName("td")[j]; 181 | } 182 | 183 | function clearBoard() { 184 | for (let i = 0; i < board.length; i++) { 185 | for (let j = 0; j < board[0].length; j++) { 186 | board[i][j] = -1; 187 | getLegionCell(i, j).style.background = pieceColours.get(board[i][j]) 188 | } 189 | } 190 | boardFilled = 0; 191 | localStorage.setItem("legionBoard", JSON.stringify(board)); 192 | localStorage.setItem("boardFilled", JSON.stringify(0)); 193 | document.getElementById('boardFilledValue').innerText = `${boardFilled}`; 194 | } 195 | 196 | function setBoard(i, j, value) { 197 | if (state != states.START) { 198 | return; 199 | } 200 | 201 | if (isBigClick) { 202 | if (value == 0) { 203 | for (let point of legionGroups[findGroupNumber(i, j)]) { 204 | let grid = getLegionCell(point.x, point.y); 205 | grid.style.background = pieceColours.get(0); 206 | if (board[point.x][point.y] == -1) { 207 | boardFilled++; 208 | } 209 | board[point.x][point.y] = 0; 210 | } 211 | } else { 212 | for (let point of legionGroups[findGroupNumber(i, j)]) { 213 | let grid = getLegionCell(point.x, point.y); 214 | grid.style.background = pieceColours.get(-1); 215 | if (board[point.x][point.y] == 0) { 216 | boardFilled--; 217 | } 218 | board[point.x][point.y] = -1; 219 | } 220 | } 221 | } else { 222 | let grid = getLegionCell(i, j); 223 | if (value == -1) { 224 | if (board[i][j] != -1) { 225 | board[i][j] = -1; 226 | grid.style.background = pieceColours.get(-1); 227 | boardFilled--; 228 | } 229 | } else { 230 | if (board[i][j] != 0) { 231 | board[i][j] = 0; 232 | grid.style.background = pieceColours.get(0); 233 | boardFilled++; 234 | } 235 | } 236 | } 237 | localStorage.setItem("legionBoard", JSON.stringify(board)); 238 | localStorage.setItem("boardFilled", JSON.stringify(boardFilled)); 239 | document.getElementById('boardFilledValue').innerText = `${boardFilled}`; 240 | } 241 | 242 | function hoverOverBoard(i, j) { 243 | if (state != states.START) { 244 | return; 245 | } 246 | if (isBigClick) { 247 | for (let point of legionGroups[findGroupNumber(i, j)]) { 248 | if (board[point.x][point.y] == -1) { 249 | if (isDarkMode) { 250 | getLegionCell(point.x, point.y).style.background = 'dimgrey'; 251 | } else { 252 | getLegionCell(point.x, point.y).style.background = 'silver'; 253 | } 254 | } else { 255 | if (isDarkMode) { 256 | getLegionCell(point.x, point.y).style.background = 'rgb(20, 20, 20)'; 257 | } else { 258 | getLegionCell(point.x, point.y).style.background = 'dimgrey'; 259 | } 260 | 261 | } 262 | 263 | } 264 | } else { 265 | if (board[i][j] == -1) { 266 | if (isDarkMode) { 267 | getLegionCell(i, j).style.background = 'dimgrey'; 268 | } else { 269 | getLegionCell(i, j).style.background = 'silver'; 270 | } 271 | } else { 272 | if (isDarkMode) { 273 | getLegionCell(i, j).style.background = 'rgb(20, 20, 20)'; 274 | } else { 275 | getLegionCell(i, j).style.background = 'dimgrey'; 276 | } 277 | } 278 | 279 | } 280 | } 281 | 282 | function hoverOffBoard(i, j) { 283 | if (state != states.START) { 284 | return; 285 | } 286 | if (isBigClick) { 287 | for (let point of legionGroups[findGroupNumber(i, j)]) { 288 | if (board[point.x][point.y] == -1) { 289 | getLegionCell(point.x, point.y).style.background = pieceColours.get(-1); 290 | } else { 291 | getLegionCell(point.x, point.y).style.background = pieceColours.get(0); 292 | } 293 | } 294 | } else { 295 | if (board[i][j] == -1) { 296 | getLegionCell(i, j).style.background = pieceColours.get(-1); 297 | } else { 298 | getLegionCell(i, j).style.background = pieceColours.get(0); 299 | } 300 | } 301 | } 302 | 303 | function resetBoard() { 304 | for (let k = 0; k < legionSolvers.length; k++) { 305 | for (let i = 0; i < legionSolvers[k].board.length; i++) { 306 | for (let j = 0; j < legionSolvers[k].board[0].length; j++) { 307 | if (k == 0) { 308 | getLegionCell(i, j).style.borderWidth = '1px'; 309 | if (legionSolvers[k].board[i][j] >= 0) { 310 | getLegionCell(i, j).style.background = pieceColours.get(0); 311 | legionSolvers[k].board[i][j] = 0; 312 | } 313 | } else { 314 | if (legionSolvers[k].board[i][j] >= 0) { 315 | legionSolvers[k].board[i][j] = 0; 316 | } 317 | } 318 | } 319 | } 320 | } 321 | 322 | 323 | setLegionBorders(); 324 | legionSolvers = []; 325 | } 326 | 327 | function drawBoard() { 328 | setLegionBorders(); 329 | colourBoard(); 330 | } 331 | 332 | function colourBoard() { 333 | let spot; 334 | for (let i = 0; i < board.length; i++) { 335 | for (let j = 0; j < board[0].length; j++) { 336 | spot = board[i][j]; 337 | getLegionCell(i, j).style.background = pieceColours.get(spot); 338 | } 339 | } 340 | 341 | if (pieceHistory.length == 0 && legionSolvers[0]) { 342 | pieceHistory = legionSolvers[0].history; 343 | } 344 | 345 | for (let piece of pieceHistory) { 346 | for (let i = 0; i < piece.length; i++) { 347 | if (board[piece[i].y][piece[i].x - 1] > 0 && (getLegionCell(piece[i].y, piece[i].x).style.borderLeftWidth == '3px' || getLegionCell(piece[i].y, piece[i].x - 1).style.borderRightWidth == '3px')) { 348 | getLegionCell(piece[i].y, piece[i].x).style.borderLeftWidth = '1px'; 349 | getLegionCell(piece[i].y, piece[i].x - 1).style.borderRightWidth = '1px'; 350 | } 351 | if (board[piece[i].y - 1] && board[piece[i].y - 1][piece[i].x] > 0 && (getLegionCell(piece[i].y, piece[i].x).style.borderTopWidth == '3px' || getLegionCell(piece[i].y - 1, piece[i].x).style.borderBottomWidth == '3px' )) { 352 | getLegionCell(piece[i].y, piece[i].x).style.borderTopWidth = '1px'; 353 | getLegionCell(piece[i].y - 1, piece[i].x).style.borderBottomWidth = '1px'; 354 | } 355 | for (let j = 0; j < piece.length; j++) { 356 | if (i != j && piece[i].x - 1 == piece[j].x && piece[i].y == piece[j].y) { 357 | getLegionCell(piece[i].y, piece[i].x).style.borderLeftWidth = '0px'; 358 | if (board[0][piece[i].x - 1]) { 359 | getLegionCell(piece[i].y, piece[i].x - 1).style.borderRightWidth = '0px'; 360 | } 361 | } 362 | if (i != j && piece[i].x == piece[j].x && piece[i].y - 1 == piece[j].y) { 363 | getLegionCell(piece[i].y, piece[i].x).style.borderTopWidth = '0px'; 364 | if (board[piece[i].y - 1]) { 365 | getLegionCell(piece[i].y - 1, piece[i].x).style.borderBottomWidth = '0px'; 366 | } 367 | } 368 | } 369 | } 370 | } 371 | } 372 | 373 | function activateDarkMode() { 374 | isDarkMode = !isDarkMode; 375 | localStorage.setItem("isDarkMode", JSON.stringify(isDarkMode)); 376 | let cell; 377 | let switchTo; 378 | if (isDarkMode) { 379 | switchTo = 'white'; 380 | document.getElementById("body").style.backgroundColor = 'rgb(54, 57, 63)'; 381 | for (let i = 0 ; i < pieces.length; i++) { 382 | document.getElementById(`piece${i+1}`).style.backgroundColor = 'silver'; 383 | } 384 | pieceColours.set(-1, 'grey'); 385 | pieceColours.set(0, 'rgb(50, 50, 50)'); 386 | } else { 387 | switchTo = 'black'; 388 | document.getElementById("body").style.backgroundColor = 'white'; 389 | for (let i = 0 ; i < pieces.length; i++) { 390 | document.getElementById(`piece${i+1}`).style.backgroundColor = 'white'; 391 | } 392 | pieceColours.set(-1, 'white'); 393 | pieceColours.set(0, 'grey'); 394 | } 395 | drawBoard(); 396 | for (let i = 0; i < board.length; i++) { 397 | for (let j = 0; j < board[0].length; j++) { 398 | cell = getLegionCell(i, j); 399 | if (cell.style.borderTopColor != switchTo) { 400 | cell.style.borderTopColor = switchTo 401 | } 402 | if (cell.style.borderBottomColor != switchTo) { 403 | cell.style.borderBottomColor = switchTo 404 | } 405 | if (cell.style.borderRightColor != switchTo) { 406 | cell.style.borderRightColor = switchTo 407 | } 408 | if (cell.style.borderLeftColor != switchTo) { 409 | cell.style.borderLeftColor = switchTo 410 | } 411 | } 412 | } 413 | document.getElementById("body").style.color = switchTo; 414 | } 415 | 416 | function activateBigClick() { 417 | isBigClick = !isBigClick; 418 | localStorage.setItem("isBigClick", JSON.stringify(isBigClick)); 419 | } 420 | 421 | function activateLiveSolve() { 422 | isLiveSolve = !isLiveSolve; 423 | localStorage.setItem("isLiveSolve", JSON.stringify(isLiveSolve)); 424 | if (isLiveSolve && state != states.COMPLETED) { 425 | drawBoard(); 426 | } 427 | } 428 | 429 | function reset() { 430 | resetBoard(); 431 | document.getElementById("clearBoard").disabled = false; 432 | document.getElementById("boardButton").innerText = i18n("start"); 433 | document.getElementById("resetButton").style.visibility = 'hidden'; 434 | document.getElementById("iterations").style.visibility = 'hidden'; 435 | document.getElementById("time").style.visibility = 'hidden'; 436 | document.getElementById("failText").style.visibility = 'hidden'; 437 | pieceHistory = []; 438 | state = states.START; 439 | } 440 | 441 | async function handleButton(evt) { 442 | switch (state) { 443 | case states.START: 444 | evt.target.innerText = i18n("pause"); 445 | document.getElementById("clearBoard").disabled = true; 446 | state = states.RUNNING; 447 | let success = await runSolver(); 448 | if (!success) { 449 | document.getElementById("failText").style.visibility = 'visible'; 450 | } 451 | evt.target.innerText = i18n("reset"); 452 | state = states.COMPLETED; 453 | break; 454 | case states.RUNNING: 455 | evt.target.innerText = i18n("continue"); 456 | for (let solvers of legionSolvers) { 457 | solvers.pause(); 458 | } 459 | state = states.PAUSED; 460 | document.getElementById("resetButton").style.visibility = 'visible'; 461 | break; 462 | case states.PAUSED: 463 | evt.target.innerText = i18n("pause"); 464 | pieceHistory = []; 465 | for (let solvers of legionSolvers) { 466 | solvers.continue(); 467 | } 468 | state = states.RUNNING 469 | document.getElementById("resetButton").style.visibility = 'hidden'; 470 | break; 471 | case states.COMPLETED: 472 | reset(); 473 | break; 474 | } 475 | } 476 | 477 | async function runSolver() { 478 | if (boardFilled == 0 && currentPieces > 0) { 479 | return false; 480 | } 481 | let downBoard = []; 482 | for (let i = 0; i < board.length; i++) { 483 | downBoard[i] = []; 484 | for (let j = 0; j < board[0].length; j++) { 485 | downBoard[i][j] = board[board.length - 1 - i][board[0].length - 1 - j]; 486 | } 487 | } 488 | let rightBoard = []; 489 | for (let i = 0; i < board[0].length; i++) { 490 | rightBoard[i] = []; 491 | for (let j = 0; j < board.length; j++) { 492 | rightBoard[i][j] = board[board.length - j - 1][i]; 493 | } 494 | } 495 | let leftBoard = []; 496 | for (let i = 0; i < board[0].length; i++) { 497 | leftBoard[i] = []; 498 | for (let j = 0; j < board.length; j++) { 499 | leftBoard[i][j] = board[j][board[0].length - 1 - i]; 500 | } 501 | } 502 | 503 | pieceHistory = []; 504 | legionSolvers.push(new LegionSolver(board, _.cloneDeep(pieces), onBoardUpdated)); 505 | legionSolvers.push(new LegionSolver(rightBoard, _.cloneDeep(pieces), () => false)); 506 | legionSolvers.push(new LegionSolver(downBoard, _.cloneDeep(pieces), () => false)); 507 | legionSolvers.push(new LegionSolver(leftBoard, _.cloneDeep(pieces), () => false)); 508 | 509 | let runRotated = legionSolvers[0].longSpaces.length != 0; 510 | const boardPromise = legionSolvers[0].solve(); 511 | let success; 512 | if (runRotated) { 513 | const rightBoardPromise = legionSolvers[1].solve(); 514 | const downBoardPromise = legionSolvers[2].solve(); 515 | const leftBoardPromise = legionSolvers[3].solve(); 516 | success = await Promise.race([boardPromise, rightBoardPromise, downBoardPromise, leftBoardPromise]); 517 | } else { 518 | success = await boardPromise; 519 | } 520 | 521 | for (let solver of legionSolvers) { 522 | solver.stop(); 523 | } 524 | 525 | let finishedSolver; 526 | 527 | if (legionSolvers[0].success !== undefined) { 528 | for (let i = 0; i < legionSolvers[0].board.length; i++) { 529 | for (let j = 0; j < legionSolvers[0].board[0].length; j++) { 530 | board[i][j] = legionSolvers[0].board[i][j]; 531 | } 532 | } 533 | finishedSolver = legionSolvers[0]; 534 | pieceHistory = legionSolvers[0].history; 535 | } else if (legionSolvers[1].success !== undefined) { 536 | for (let i = 0; i < legionSolvers[1].board[0].length; i++) { 537 | for (let j = 0; j < legionSolvers[1].board.length; j++) { 538 | board[i][j] = legionSolvers[1].board[j][legionSolvers[1].board[0].length - 1 - i]; 539 | } 540 | } 541 | 542 | for (let piece of legionSolvers[1].history) { 543 | for (let point of piece) { 544 | let holder = point.y 545 | point.y = legionSolvers[1].board[0].length - 1 - point.x 546 | point.x = holder; 547 | } 548 | } 549 | finishedSolver = legionSolvers[1]; 550 | pieceHistory = legionSolvers[1].history 551 | } else if (legionSolvers[2].success !== undefined) { 552 | for (let i = 0; i < legionSolvers[2].board.length; i++) { 553 | for (let j = 0; j < legionSolvers[2].board[0].length; j++) { 554 | board[i][j] = legionSolvers[2].board[legionSolvers[2].board.length - 1 - i][legionSolvers[2].board[0].length - 1 - j]; 555 | } 556 | } 557 | 558 | for (let piece of legionSolvers[2].history) { 559 | for (let point of piece) { 560 | point.y = legionSolvers[2].board.length - 1 - point.y 561 | point.x = legionSolvers[2].board[0].length - 1 - point.x 562 | } 563 | } 564 | finishedSolver = legionSolvers[2]; 565 | pieceHistory = legionSolvers[2].history 566 | } else if (legionSolvers[3].success !== undefined) { 567 | for (let i = 0; i < legionSolvers[3].board[0].length; i++) { 568 | for (let j = 0; j < legionSolvers[3].board.length; j++) { 569 | board[i][j] = legionSolvers[3].board[legionSolvers[3].board.length - j - 1][i]; 570 | } 571 | } 572 | 573 | for (let piece of legionSolvers[3].history) { 574 | for (let point of piece) { 575 | let holder = point.x 576 | point.x = legionSolvers[3].board.length - 1 - point.y 577 | point.y = holder 578 | } 579 | } 580 | finishedSolver = legionSolvers[3]; 581 | pieceHistory = legionSolvers[3].history 582 | } 583 | 584 | document.getElementById("iterations").style.visibility = 'visible'; 585 | document.getElementById("iterationsValue").innerText = `${finishedSolver.iterations}`; 586 | 587 | document.getElementById("time").style.visibility = 'visible'; 588 | document.getElementById("timeValue").innerText = `${new Date().getTime() - finishedSolver.time}ms`; 589 | if (success) { 590 | drawBoard(); 591 | } 592 | return success; 593 | } 594 | 595 | function onBoardUpdated() { 596 | if (isLiveSolve) { 597 | drawBoard(); 598 | } 599 | } 600 | 601 | export { pieceColours }; --------------------------------------------------------------------------------