From df38c0f1121b4b0d778180b1f29455054600a58b Mon Sep 17 00:00:00 2001 From: Michael Fuller Date: Fri, 6 May 2022 20:09:11 -0500 Subject: [PATCH] Update Old Chou's Solver (#182) 1. Solver and UI update Co-authored-by: Michael Fuller --- .../old-chou-treasure-button.component.ts | 3 +- .../old-chou-treasure.component.html | 73 ++++- .../shared/chou-treasure-grid-item.spec.ts | 7 + src/app/shared/chou-treasure-grid-item.ts | 22 ++ src/app/shared/chou-treasure-grid.ts | 274 +++++++++++++++--- 5 files changed, 327 insertions(+), 52 deletions(-) create mode 100644 src/app/shared/chou-treasure-grid-item.spec.ts create mode 100644 src/app/shared/chou-treasure-grid-item.ts diff --git a/src/app/old-chou-treasure-button/old-chou-treasure-button.component.ts b/src/app/old-chou-treasure-button/old-chou-treasure-button.component.ts index adb7679..9e669be 100644 --- a/src/app/old-chou-treasure-button/old-chou-treasure-button.component.ts +++ b/src/app/old-chou-treasure-button/old-chou-treasure-button.component.ts @@ -39,7 +39,7 @@ export class OldChouTreasureButtonComponent implements OnInit { let mode = ''; - switch (value) { + switch (value.classification) { case ChouTreasureTypes.BOMB: this.label = 'BOMB'; mode = 'barrel'; @@ -88,7 +88,6 @@ export class OldChouTreasureButtonComponent implements OnInit { ngOnInit(): void {} buttonPressed() { - console.log('C1'); this.cellClicked.emit(this.y * 5 + this.x); } } diff --git a/src/app/old-chou-treasure/old-chou-treasure.component.html b/src/app/old-chou-treasure/old-chou-treasure.component.html index 77e6720..91507e2 100644 --- a/src/app/old-chou-treasure/old-chou-treasure.component.html +++ b/src/app/old-chou-treasure/old-chou-treasure.component.html @@ -20,7 +20,76 @@

Old Chou's Treasure Solver

of Minesweeper. The only real difference is each spot can only have 1 or 2 bombs nearby and it only looks at spots in the “up”, “right”, “down” and "left" position. So, in game dig out one of the corners and see what happens - and make this map match. So, if you get Cabbage, click that spot until it’s - cabbage. The goal is to find the Treasure, which is hidden between 4 bombs. + and make this map match. So, if you get Cabbage, click that spot until it + displays a cabbage icon. The goal is to find the Treasure, which is hidden + between 4 bombs or on the edge surrounded by bombs. + +

Icon Description

+ +
+
+ ... +
+
Unknown Tile
+

This tile is a mystery.

+
+
+ +
+ ... +
+
Empty Tile
+

+ This tile has no bombs on the N, + E, S, + W edges. +

+
+
+ +
+ ... +
+
Cabbage Tile
+

+ This tile has a total of one bomb on either the + N, E, + S, W edges. +

+
+
+ +
+ ... +
+
Iron Tile
+

+ This tile has a total of two bombs on either the + N, E, + S, W edges. +

+
+
+ +
+ ... +
+
Question Tile
+

The program is unsure what is under this tile.

+
+
+ +
+ ... +
+
Safe Tile
+

The program is sure this tile is safe to dig.

+
+
+
diff --git a/src/app/shared/chou-treasure-grid-item.spec.ts b/src/app/shared/chou-treasure-grid-item.spec.ts new file mode 100644 index 0000000..176bc93 --- /dev/null +++ b/src/app/shared/chou-treasure-grid-item.spec.ts @@ -0,0 +1,7 @@ +import { ChouTreasureGridItem } from './chou-treasure-grid-item'; + +describe('ChouTreasureGridItem', () => { + it('should create an instance', () => { + expect(new ChouTreasureGridItem()).toBeTruthy(); + }); +}); diff --git a/src/app/shared/chou-treasure-grid-item.ts b/src/app/shared/chou-treasure-grid-item.ts new file mode 100644 index 0000000..161a680 --- /dev/null +++ b/src/app/shared/chou-treasure-grid-item.ts @@ -0,0 +1,22 @@ +import { ChouTreasureTypes } from './chou-treasure-types'; + +export class ChouTreasureGridItem { + public edgeBombs: number = 0; + public edgeUnknown: number = 0; + public safe: boolean = false; + public solved: boolean = false; + public user: boolean = false; + + constructor( + public x: number = 0, + public y: number = 0, + public classification: ChouTreasureTypes = ChouTreasureTypes.UNKNOWN + ) {} + + public reset() { + this.edgeBombs = 0; + this.edgeUnknown = 0; + this.safe = false; + this.solved = false; + } +} diff --git a/src/app/shared/chou-treasure-grid.ts b/src/app/shared/chou-treasure-grid.ts index cb316f0..747e4fb 100644 --- a/src/app/shared/chou-treasure-grid.ts +++ b/src/app/shared/chou-treasure-grid.ts @@ -1,49 +1,32 @@ +import { ChouTreasureGridItem } from './chou-treasure-grid-item'; import { ChouTreasureTypes } from './chou-treasure-types'; +declare type eachCallback = ( + x: number, + y: number, + item: ChouTreasureGridItem, + neighbors: Array +) => boolean; + export class ChouTreasureGrid { - public grid: Array> = [[]]; + public grid: Array> = []; + + private static FAKEITEM: ChouTreasureGridItem = new ChouTreasureGridItem( + 0, + 0, + ChouTreasureTypes.FAKE + ); constructor(previous: ChouTreasureGrid | undefined) { if (previous) { this.grid = previous.grid; } else { - this.grid = [ - [ - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ], - [ - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ], - [ - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ], - [ - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ], - [ - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ChouTreasureTypes.UNKNOWN, - ], - ]; + for (let y = 0; y < 5; y++) { + this.grid.push([]); + for (let x = 0; x < 5; x++) { + this.grid[y].push(new ChouTreasureGridItem(x, y)); + } + } } } @@ -51,6 +34,13 @@ export class ChouTreasureGrid { if (x < 0 || x >= 5 || y < 0 || y >= 5) { return ChouTreasureTypes.FAKE; } + return this.grid[y][x].classification; + } + + private getItem(x: number, y: number): ChouTreasureGridItem { + if (x < 0 || x >= 5 || y < 0 || y >= 5) { + return ChouTreasureGrid.FAKEITEM; + } return this.grid[y][x]; } @@ -58,7 +48,7 @@ export class ChouTreasureGrid { if (x < 0 || x >= 5 || y < 0 || y >= 5) { return ChouTreasureTypes.FAKE; } - let value = this.grid[y][x]; + let value = this.grid[y][x].classification; switch (value) { case ChouTreasureTypes.ORE: @@ -71,11 +61,42 @@ export class ChouTreasureGrid { } } + public each(callback: eachCallback) { + let again = true; + while (again) { + again = false; + for (let y = 0; y < this.grid.length; y++) { + for (let x = 0; x < this.grid.length; x++) { + let nextTo: Array = []; + let temp = this.getItem(x, y - 1); + if (temp.classification != ChouTreasureTypes.FAKE) { + nextTo.push(temp); + } + temp = this.getItem(x, y + 1); + if (temp.classification != ChouTreasureTypes.FAKE) { + nextTo.push(temp); + } + temp = this.getItem(x - 1, y); + if (temp.classification != ChouTreasureTypes.FAKE) { + nextTo.push(temp); + } + temp = this.getItem(x + 1, y); + if (temp.classification != ChouTreasureTypes.FAKE) { + nextTo.push(temp); + } + if (callback(x, y, this.grid[y][x], nextTo)) { + again = true; + } + } + } + } + } + private sniffSpot2(x: number, y: number): ChouTreasureTypes { if (x < 0 || x >= 5 || y < 0 || y >= 5) { return ChouTreasureTypes.FAKE; } - let value = this.grid[y][x]; + let value = this.grid[y][x].classification; switch (value) { case ChouTreasureTypes.ORE: @@ -93,14 +114,14 @@ export class ChouTreasureGrid { private forceSpot(x: number, y: number, value: ChouTreasureTypes) { if (x < 0 || x >= 5 || y < 0 || y >= 5) { } else { - this.grid[y][x] = value; + this.grid[y][x].classification = value; } } public updateSpot(x: number, y: number) { this.clean(); - let currentValue = this.grid[y][x]; + let currentValue = this.grid[y][x].classification; let futureValue = ChouTreasureTypes.EMPTY; switch (currentValue) { case ChouTreasureTypes.EMPTY: @@ -109,17 +130,168 @@ export class ChouTreasureGrid { case ChouTreasureTypes.CABBAGE: futureValue = ChouTreasureTypes.ORE; break; + case ChouTreasureTypes.ORE: + futureValue = ChouTreasureTypes.UNKNOWN; + break; default: futureValue = ChouTreasureTypes.EMPTY; break; } - this.grid[y][x] = futureValue; + this.grid[y][x].classification = futureValue; + // This cell has been interacted with + this.grid[y][x].user = + this.grid[y][x].classification != ChouTreasureTypes.UNKNOWN; let g = this.grid; + // Mark Safe Columns + + this.each((x, y, item, neighbors) => { + if (item.classification == ChouTreasureTypes.EMPTY) { + for (let nextTo of neighbors) { + nextTo.safe = true; + if (nextTo.classification == ChouTreasureTypes.UNKNOWN) { + //console.log(`Mark Safe (${nextTo.x},${nextTo.y})`); + nextTo.classification = ChouTreasureTypes.SAFE_SPOT; + } + } + } + return false; + }); + // Count the number of bombs are wanted for each tile + this.each((x, y, item, neighbors) => { + if (item.solved) { + return false; + } + + if ( + item.classification == ChouTreasureTypes.CABBAGE || + item.classification == ChouTreasureTypes.ORE + ) { + if ( + item.classification == ChouTreasureTypes.ORE && + neighbors.length == 2 + ) { + // If the Ore is on the Edge, the two adjancent tiles are bombs + item.solved = true; + for (let nextTo of neighbors) { + nextTo.classification = ChouTreasureTypes.BOMB; + nextTo.safe = false; + nextTo.edgeBombs += 100; + } + } else { + let desiredBombs: number = + item.classification == ChouTreasureTypes.CABBAGE ? 1 : 2; + + let availableTiles: number = 0; + let bombTiles: number = 0; + let availableItems: Array = []; + for (let nextTo of neighbors) { + if (nextTo.classification == ChouTreasureTypes.BOMB) { + bombTiles++; + //availableTiles++; + } else if (nextTo.safe == true || nextTo.user == true) { + // Skip. this tile is safe or user set, which can't be a bomb + } else { + availableTiles++; + availableItems.push(nextTo); + } + } + + if (desiredBombs == bombTiles) { + // My needs are satisified, every other tile should be freed + item.solved = true; + for (let nextTo of availableItems) { + nextTo.classification = ChouTreasureTypes.SAFE_SPOT; + } + } else if ( + (bombTiles == 0 && availableTiles == desiredBombs) || + (bombTiles > 0 && + availableTiles > 0 && + availableTiles == desiredBombs - bombTiles) + ) { + item.solved = true; + // The next door neighbors are actually bombs + for (let nextTo of availableItems) { + nextTo.classification = ChouTreasureTypes.BOMB; + } + return true; + } else { + let magnitude: number = 25; + switch (availableItems.length) { + case 1: + { + magnitude = 100; + } + break; + case 2: + { + magnitude = 50; + } + break; + case 3: + { + magnitude = 33; + } + break; + } + for (let nextTo of availableItems) { + nextTo.classification = ChouTreasureTypes.POTENTIAL_BOMB; + nextTo.edgeBombs += magnitude; + } + } + } + } + return false; + }); + + // Fix things + this.each((x, y, item, neighbors) => { + if ( + item.solved == false && + item.classification == ChouTreasureTypes.CABBAGE + ) { + let lowEffort: Array = []; + + let availableItems: Array = []; + for (let nextTo of neighbors) { + if (nextTo.classification == ChouTreasureTypes.BOMB) { + } else if (nextTo.safe == true || nextTo.user == true) { + // Skip. this tile is safe or user set, which can't be a bomb + } else { + if (nextTo.edgeBombs <= 50) { + lowEffort.push(nextTo); + } + availableItems.push(nextTo); + } + } + if (availableItems.length > lowEffort.length) { + for (let nextTo of lowEffort) { + nextTo.classification = ChouTreasureTypes.SAFE_SPOT; + nextTo.edgeBombs = 0; + } + } + } else if ( + item.solved == false && + item.classification == ChouTreasureTypes.UNKNOWN + ) { + let bombTiles: number = 0; + for (let nextTo of neighbors) { + if (nextTo.classification == ChouTreasureTypes.BOMB) { + bombTiles++; + } + } + if (bombTiles == neighbors.length) { + item.classification = ChouTreasureTypes.TREASURE; + } + } + return false; + }); + + /* for (y = 0; y < g.length; y++) { for (x = 0; x < g.length; x++) { - currentValue = g[y][x]; + currentValue = g[y][x].classification; let up = this.sniffSpot(x, y - 1); let down = this.sniffSpot(x, y + 1); @@ -152,7 +324,7 @@ export class ChouTreasureGrid { let changed = false; for (y = 0; y < g.length; y++) { for (x = 0; x < g.length; x++) { - currentValue = g[y][x]; + currentValue = g[y][x].classification; let up = this.sniffSpot2(x, y - 1); let down = this.sniffSpot2(x, y + 1); @@ -362,12 +534,16 @@ export class ChouTreasureGrid { break; } } + */ } public reset() { for (let y = 0; y < this.grid!.length; y++) { for (let x = 0; x < this.grid![y].length; x++) { - this.grid![y][x] = ChouTreasureTypes.UNKNOWN; + let item = this.grid[y][x]; + item.reset(); + item.user = false; + item.classification = ChouTreasureTypes.UNKNOWN; } } } @@ -375,13 +551,15 @@ export class ChouTreasureGrid { public clean() { for (let y = 0; y < this.grid.length; y++) { for (let x = 0; x < this.grid[y].length; x++) { - switch (this.grid[y][x]) { + let item = this.grid[y][x]; + item.reset(); + switch (item.classification) { case ChouTreasureTypes.EMPTY: case ChouTreasureTypes.CABBAGE: case ChouTreasureTypes.ORE: break; default: - this.grid[y][x] = ChouTreasureTypes.UNKNOWN; + item.classification = ChouTreasureTypes.UNKNOWN; break; } }