Skip to content

Commit d8535fd

Browse files
committed
Initial attempt at auto solving; TS migration
1 parent 08d66fc commit d8535fd

24 files changed

+913
-123
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.vscode
2+
node_modules
3+
package-lock.json

app.js

+63-68
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,78 @@
1-
const BLANK = 0;
2-
const FILLED = 1;
3-
const MAX_CAP = 50;
1+
import Renderer from './dist/Renderer.js';
2+
import { ModelType } from './dist/Types.js';
3+
import { TESTS } from './dist/TestData.js';
44

5-
function arrayRotate(arr, reverse) {
6-
if (reverse) arr.unshift(arr.pop());
7-
else arr.push(arr.shift());
8-
return arr;
9-
}
10-
11-
const vm = Vue.createApp({
5+
const app = Vue.createApp({
126
data() {
13-
return { width: 5, pattern: null, fillCounts: [], expanded: false };
14-
},
15-
computed: {
16-
initialArray() {
17-
const result = new Array(this.width).fill(BLANK);
7+
return {
8+
renderer: null,
9+
viewWidth: 0,
10+
viewHeight: 0,
11+
viewModel: [],
1812

19-
if (!this.pattern) {
20-
return result.slice(0, MAX_CAP);
21-
}
13+
isValid: null,
2214

23-
this.pattern.split(' ').forEach((element) => {
24-
const tailIndex = result.lastIndexOf(FILLED) + 1;
25-
const repeats = parseInt(element);
26-
const start = tailIndex + !!tailIndex;
27-
if (start >= result.length) {
28-
return;
29-
}
30-
result.fill(1, start, start + repeats);
31-
});
15+
isAutoStep: false,
16+
timerId: null,
3217

33-
return result.slice(0, MAX_CAP);
18+
modelType: ModelType.MANUAL,
19+
20+
testData: TESTS,
21+
selectedData: 5
22+
};
23+
},
24+
methods: {
25+
nextStep() {
26+
// for (let i = 0; i < 1000; i++) {
27+
this.renderer.nextStep();
28+
this.validate();
29+
// }
30+
this.viewModel = this.renderer.getModel();
3431
},
35-
combinationsArray() {
36-
const result = [];
3732

38-
if (!this.pattern) {
39-
return result.slice(0, MAX_CAP);
33+
validate() {
34+
this.isValid = this.renderer.validateModel();
35+
if (this.isValid) {
36+
this.viewModel = this.renderer.getModel();
37+
clearInterval(this.timerId);
4038
}
39+
},
4140

42-
let lastCombination = this.initialArray.slice(0);
43-
let sliceEnd = lastCombination.length;
44-
this.pattern
45-
.split(' ')
46-
.reverse()
47-
.forEach((item) => {
48-
const itemLength = parseInt(item);
49-
const sliceStart = lastCombination.slice(0, sliceEnd).lastIndexOf(FILLED) - (itemLength - 1);
50-
const sliceLength = sliceEnd - sliceStart;
51-
52-
let slice = lastCombination.slice(0).splice(sliceStart, sliceLength);
53-
for (let i = 0; i < sliceLength - itemLength; i++) {
54-
arrayRotate(slice, true);
55-
lastCombination.splice(sliceStart, slice.length, ...slice);
56-
result.push(lastCombination.slice(0));
57-
}
58-
59-
sliceEnd = sliceEnd - (itemLength + 1);
60-
});
61-
62-
return result;
41+
init() {
42+
this.viewModel = this.renderer.getModel();
43+
const { rows, columns } = this.renderer.getSize();
44+
this.viewHeight = rows;
45+
this.viewWidth = columns;
46+
}
47+
},
48+
watch: {
49+
selectedData(dataIndex) {
50+
this.renderer = new Renderer(TESTS[dataIndex]);
51+
this.init();
6352
},
64-
unitedArray() {
65-
this.fillCounts = new Array(this.initialArray.length).fill(0);
66-
this.initialArray.map((val, index) => {
67-
this.fillCounts[index] = val;
68-
});
53+
isAutoStep(state) {
54+
if (this.timerId && !state) {
55+
clearInterval(this.timerId);
56+
}
6957

70-
return this.combinationsArray.reduce((acc, combination) => {
71-
return acc.map((val, ind) => {
72-
this.fillCounts[ind] += combination[ind];
73-
return val && combination[ind];
74-
});
75-
}, this.initialArray.slice(0));
58+
if (state) {
59+
this.timerId = setInterval(() => {
60+
this.nextStep();
61+
}, 1);
62+
}
7663
}
7764
},
78-
methods: {
79-
toggleExpand() {
80-
this.expanded = !this.expanded;
65+
beforeMount() {
66+
this.renderer = new Renderer(TESTS[this.selectedData]);
67+
},
68+
mounted() {
69+
this.init();
70+
},
71+
unmounted() {
72+
if (this.timerId) {
73+
clearInterval(this.timerId);
8174
}
8275
}
83-
}).mount('#app');
76+
});
77+
78+
app.mount('#app');

dist/Board.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
var TileType;
2+
(function (TileType) {
3+
TileType[TileType["EMPTY"] = 0] = "EMPTY";
4+
TileType[TileType["FILLED"] = 1] = "FILLED";
5+
})(TileType || (TileType = {}));
6+
const PatterType = {
7+
[TileType[TileType.EMPTY]]: `${TileType.EMPTY}`,
8+
[TileType[TileType.FILLED]]: `${TileType.FILLED}`
9+
};
10+
export { TileType, PatterType };

dist/Helpers.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { TileType } from './Types.js';
2+
function getEmptyRawDataArray(rows, cols) {
3+
return new Array(cols).fill(new Array(rows).fill(TileType.EMPTY));
4+
}
5+
function transposeRawData(matrix) {
6+
return matrix[0].map((_, i) => matrix.map((row) => row[i]));
7+
}
8+
function mergeRawData(mat1, mat2) {
9+
return mat1.map((r, i) => r.map((v, j) => Number(!!(v + mat2[i][j]))));
10+
}
11+
function rawDataToPattern(rawData) {
12+
let valueCounter = 0;
13+
return rawData.map((row) => {
14+
const result = [];
15+
for (let i = 0; i < row.length; i++) {
16+
if (row[i] === TileType.FILLED) {
17+
valueCounter += 1;
18+
}
19+
if ((i === row.length - 1 || row[i] === TileType.EMPTY) && valueCounter) {
20+
result.push(valueCounter);
21+
valueCounter = 0;
22+
}
23+
}
24+
return result;
25+
});
26+
}
27+
function isEqualRawSet(p1, p2) {
28+
return p1.every((pattern, index) => {
29+
return pattern.join('-') === p2[index].join('-');
30+
});
31+
}
32+
const combinations = (sets) => {
33+
if (sets.length === 1) {
34+
return sets[0].map((el) => [el]);
35+
}
36+
else
37+
return sets[0].flatMap((val) => combinations(sets.slice(1)).map((c) => [val].concat(c)));
38+
};
39+
export { getEmptyRawDataArray, transposeRawData, mergeRawData, rawDataToPattern, isEqualRawSet, combinations };

dist/Helpers.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/Render.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/Renderer.js

+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { TileType } from './Types.js';
2+
import { isEqualRawSet, rawDataToPattern, transposeRawData } from './Helpers.js';
3+
class Solver {
4+
constructor(rows, rowsLength) {
5+
this._generateCombinationsData(rows, rowsLength);
6+
}
7+
_computeBaseArrangement(pattern, length) {
8+
const result = new Array(length).fill(TileType.EMPTY);
9+
pattern.map((repeats) => {
10+
const tailIndex = result.lastIndexOf(TileType.FILLED) + 1;
11+
const start = tailIndex + Number(!!tailIndex);
12+
if (start >= result.length) {
13+
return;
14+
}
15+
result.fill(TileType.FILLED, start, start + repeats);
16+
});
17+
return result;
18+
}
19+
_computeCombinations(baseArrangement, pattern, length, includeBaseArrangement = false) {
20+
if (!pattern.length) {
21+
return [new Array(length).fill(TileType.EMPTY)];
22+
}
23+
const result = [];
24+
let lastCombination = baseArrangement.slice(0);
25+
let sliceEnd = lastCombination.length;
26+
if (includeBaseArrangement) {
27+
result.push(baseArrangement);
28+
}
29+
if (baseArrangement[baseArrangement.length - 1] === TileType.FILLED) {
30+
return result;
31+
}
32+
pattern.reverse().forEach((repeats) => {
33+
const sliceStart = lastCombination.slice(0, sliceEnd).lastIndexOf(TileType.FILLED) - (repeats - 1);
34+
const sliceLength = sliceEnd - sliceStart;
35+
let slice = lastCombination.slice(0).splice(sliceStart, sliceLength);
36+
if (sliceLength - repeats === 0) {
37+
result.push(lastCombination.slice(0));
38+
}
39+
else {
40+
for (let i = 0; i < sliceLength - repeats; i++) {
41+
slice.unshift(slice.pop());
42+
lastCombination.splice(sliceStart, slice.length, ...slice);
43+
result.push(lastCombination.slice(0));
44+
}
45+
}
46+
sliceEnd = sliceEnd - (repeats + 1);
47+
});
48+
if (!result.length) {
49+
return [new Array(length).fill(TileType.EMPTY)];
50+
}
51+
return result;
52+
}
53+
_generateCombinationsData(rows, rowsLength) {
54+
const combinationsData = new Array();
55+
rows.map((row) => {
56+
if (!row.length) {
57+
return;
58+
}
59+
const baseArrangement = this._computeBaseArrangement(row, rowsLength);
60+
combinationsData.push(this._computeCombinations(baseArrangement, row, rowsLength, true));
61+
});
62+
this._combinationsData = combinationsData;
63+
}
64+
getData() {
65+
return this._combinationsData;
66+
}
67+
}
68+
class RenderState {
69+
constructor(combinationsData) {
70+
this._state = new Array(combinationsData.length);
71+
combinationsData.forEach((combinationSet, index) => {
72+
this._state[index] = {
73+
value: 0,
74+
maxValue: combinationSet.length - 1
75+
};
76+
});
77+
this._counterPointer = 0;
78+
}
79+
increment() {
80+
this._state.every((state, index) => {
81+
if (state.value < state.maxValue) {
82+
state.value += 1;
83+
if (state.value === state.maxValue) {
84+
this._counterPointer += 1;
85+
}
86+
return false;
87+
}
88+
else {
89+
if (index < this._counterPointer) {
90+
state.value = 0;
91+
}
92+
}
93+
return true;
94+
});
95+
}
96+
getState() {
97+
return this._state.map((stateData) => stateData.value);
98+
}
99+
}
100+
class Renderer {
101+
constructor(data) {
102+
this._record = data;
103+
this._solver = new Solver(this._record.data.rows, this._record.size.rows);
104+
this._state = new RenderState(this._solver.getData());
105+
}
106+
validateModel() {
107+
const model = this.getModel();
108+
const tModel = transposeRawData(model);
109+
const testPatternSet = rawDataToPattern(tModel);
110+
const colPatternSet = this._record.data.columns;
111+
return isEqualRawSet(testPatternSet, colPatternSet);
112+
}
113+
getModel() {
114+
return this._solver
115+
.getData()
116+
.map((combinationsSet, combinationIndex) => combinationsSet[this._state.getState()[combinationIndex]]);
117+
}
118+
nextStep() {
119+
this._state.increment();
120+
}
121+
getSize() {
122+
return {
123+
rows: this._record.size.rows,
124+
columns: this._record.size.columns
125+
};
126+
}
127+
}
128+
export default Renderer;
129+
// private _mergeCombinations(combinations: TTileSet, baseArrangement: TTilePattern) {
130+
// return combinations.reduce((acc, combination) => {
131+
// return acc.map((val, ind) => {
132+
// return val && combination[ind];
133+
// });
134+
// }, baseArrangement.slice(0));
135+
// }
136+
// getRenderModel(): TTileSet {
137+
// const rows = this._record.size.rows;
138+
// const cols = this._record.size.columns;
139+
// const rowMat = new Array();
140+
// const colMat = new Array();
141+
// this._record.data.rows.map((row) => {
142+
// if (!row.length) {
143+
// return;
144+
// }
145+
// const baseArrangement = this._computeBaseArrangement(row, rows);
146+
// rowMat.push(this._mergeCombinations(this._computeCombinations(baseArrangement, row, rows), baseArrangement));
147+
// });
148+
// this._record.data.columns.map((column) => {
149+
// if (!column.length) {
150+
// return;
151+
// }
152+
// const baseArrangement = this._computeBaseArrangement(column, cols);
153+
// colMat.push(
154+
// this._mergeCombinations(this._computeCombinations(baseArrangement, column, cols), baseArrangement)
155+
// );
156+
// });
157+
// const result = mergeRawData(rowMat, transposeRawData(colMat));
158+
// return result;
159+
// }

dist/Solver.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Solver {
2+
constructor() { }
3+
}
4+
export default Solver;

dist/Solver.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)