private IList<Molecule> getCombinationsOfTwo(IList<Tile> potentialReactionTiles, Tile userClickedTile) { IList<Molecule> toReturn = new List<Molecule>(); Molecule combo; for (int i = 0; i < potentialReactionTiles.Count; i++) { for (int j = i + 1; j < potentialReactionTiles.Count; j++) { combo = new Molecule(); combo.AtomTiles.Add(potentialReactionTiles[i]); combo.AtomTiles.Add(potentialReactionTiles[j]); // Bug-fix and optimization: only molecules with the user-clicked tile are legit. if (combo.AtomTiles.Contains(userClickedTile)) { toReturn.Add(combo); } } } return toReturn; }
private IList<Molecule> calculateReactionsUsingBruteForceCombinations(IList<Tile> potentialReactionTiles, Tile userClickedTile) { IList<Molecule> toReturn = new List<Molecule>(); // Generate all possible combinations of (2 .. N) molecules. // Since the internal function handles all the combinationing, we just // call it with the biggest atom count, and it gets all the combinations of [2 .. N] IList<Molecule> combos = getCombinationsOf(potentialReactionTiles.Count, potentialReactionTiles, userClickedTile); return combos; }
private IList<Molecule> getCombinationsOf(int i, IList<Tile> potentialReactionTiles, Tile userClickedTile) { // Is there a better way? Ionno. // Let's return the FIRST molecule we find that matches. Instead of returning // like 2000 potential molecules. IList<Molecule> twos = new List<Molecule>(); IList<Molecule> threes = new List<Molecule>(); IList<Molecule> fours = new List<Molecule>(); IList<Molecule> fives = new List<Molecule>(); IList<Molecule> sixes = new List<Molecule>(); IList<Molecule> sevens = new List<Molecule>(); IList<Molecule> eights = new List<Molecule>(); IList<Molecule> nines = new List<Molecule>(); // Our "working set" of "do we have anything?" IList<Molecule> temp = new List<Molecule>(); if (i >= 2) { twos = getCombinationsOfTwo(potentialReactionTiles, userClickedTile); temp = twos.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 3) { threes = getIncrementalCombinations(potentialReactionTiles, twos); temp = threes.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 4) { // Optimize: dump un-needed stuff twos.Clear(); fours = getIncrementalCombinations(potentialReactionTiles, threes); temp = fours.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 5) { threes.Clear(); fives = getIncrementalCombinations(potentialReactionTiles, fours); temp = fives.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 6) { fours.Clear(); sixes = getIncrementalCombinations(potentialReactionTiles, fives); temp = sixes.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 7) { fives.Clear(); sevens = getIncrementalCombinations(potentialReactionTiles, sixes); temp = sevens.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 8) { sixes.Clear(); eights = getIncrementalCombinations(potentialReactionTiles, sevens); temp = eights.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } if (i >= 9) { sevens.Clear(); nines = getIncrementalCombinations(potentialReactionTiles, eights); temp = nines.Where(m => m.NetCharge == 0).ToList(); if (temp.Count > 0) { temp = weedOutNonConnectedMolecules(temp); if (temp.Count > 0) { return temp; } } } // Didn't find it yet and return? Sucks to be you! return new List<Molecule>(); }
// Our real, core algorithm. Let's do it! internal IList<Tile> GetTilesInReaction(Tile newAtomTile) { // get all adjacent atoms. True = base case IList<Tile> potentialReactionTiles = new List<Tile>(); this.getPotentialReactionTiles(newAtomTile, potentialReactionTiles); // It's fast, and it's great, but fails in wierd cases. // Like B-Fl-Fl-Fl, where you place the Fl closest to the B last. // If we carry over connected atoms, this case works, but other cases bork. // Like this case: // B Fl // B Fl // Fl IList<Molecule> potentialMolecules = this.calculateReactionsUsingBestFirstSearch(potentialReactionTiles, newAtomTile, new List<Tile>(), new List<Tile>()); // If best-first didn't find anything, revert to brute-froce. if (potentialMolecules.Count == 0) { potentialMolecules = this.calculateReactionsUsingBruteForceCombinations(potentialReactionTiles, newAtomTile); } // All of the following calls are not necessary in the brute-force case. // But the performance is trivial, so, do it anyway to simplify the code. // Weed out net != 0 potentialMolecules = potentialMolecules.Where(m => m.NetCharge == 0).ToList(); // Fix for bug where you can form |-shaped HCl around a Beryllium atom potentialMolecules = weedOutNonConnectedMolecules(potentialMolecules); // Sort by electronegativity, get the top result Molecule formedMolecule = getMostElectronegative(potentialMolecules); IList<Tile> toReturn = new List<Tile>(); if (formedMolecule != null) { foreach (Tile t in formedMolecule.AtomTiles) { toReturn.Add(t); } } return toReturn; }
// It's complicated. We use two collections: one that looks at ALL the atoms we see, ever, // and one that looks at only the atoms we've seen so far -- the latter back-pedals when // we run out of adjacencies, and the former perseveres. This covers all kinds of cases, // like (* indicates last atom placed): // B Fl // B Fl // Fl* // ... which is covered by back-pedalling only, and like: // B Cl* Cl Cl // ... which is covered by looking at the total only. private IList<Molecule> calculateReactionsUsingBestFirstSearch(IList<Tile> potentialReactionTiles, Tile centerTile, IList<Tile> foundSoFarInPath, IList<Tile>foundSoFarInTotal) { // These IFs cover a tricky case where we search exhaustively and don't find // a molecule, then when backtracking, backtrack into something we've seen // already. We shouldn't add a tile twice, and this fixes the bug that does that // (visible in puzzle one -- it adds the bottom-most Fl twice (6, 3).) if (!foundSoFarInPath.Contains(centerTile)) { foundSoFarInPath.Add(centerTile); } if (!foundSoFarInTotal.Contains(centerTile)) { foundSoFarInTotal.Add(centerTile); } Molecule m1 = new Molecule(); foreach (Tile t in foundSoFarInPath) { m1.AtomTiles.Add(t); } if (m1.NetCharge == 0) { // Base case: we're net zero! List<Molecule> toReturn = new List<Molecule>(); toReturn.Add(m1); return toReturn; } Molecule m2 = new Molecule(); foreach (Tile t in foundSoFarInTotal) { m2.AtomTiles.Add(t); } if (m2.NetCharge == 0) { // Base case: we're net zero! List<Molecule> toReturn = new List<Molecule>(); toReturn.Add(m2); return toReturn; } // Recursive case: keep looking. // Find adjacent molecules IList<Tile> adjacents = potentialReactionTiles.Where(t => TowerUtils.DistanceBetweenPoints(t.X, t.Y, centerTile.X, centerTile.Y) == 1).ToList(); // Exclude tiles we already saw. Use current path, for back-pedalling. adjacents = adjacents.Where(t => !foundSoFarInPath.Contains(t) && !foundSoFarInTotal.Contains(t)).ToList(); // Sort by |my valence + their valence|; adjacents = adjacents.OrderBy(t => t, new OrderTilesByElectronegativityWithAtomComparer(centerTile.Atom)).ToList(); // Base case: no adjacents. if (adjacents.Count == 0) { return new List<Molecule>(); } else { // Take the best, and move on foreach (Tile adj in adjacents) { // Copy foundSoFar so we can return up the stack and keep looking on // a different path. Without screwing up our "found so far" data. IList<Tile> foundDupe = new List<Tile>(); foreach (Tile t in foundSoFarInPath) { foundDupe.Add(t); } IList<Molecule> toReturn = this.calculateReactionsUsingBestFirstSearch(potentialReactionTiles, adj, foundDupe, foundSoFarInTotal); if (toReturn.Count > 0) { return toReturn; } } // Found nothing. return new List<Molecule>(); } }
private bool tileFollowsDifficultyBalancingAlgorithm(Tile current, Atom proposedAtom) { IList<Tile> adjacents = this.getNonEmptyAdjacentTiles(current); if (adjacents.Count == 0) { return true; } else { IList<Tile> oppositeValence; if (proposedAtom.IonCharge > 0) { oppositeValence = adjacents.Where(t => t.Atom.IonCharge < 0).ToList(); } else { oppositeValence = adjacents.Where(t => t.Atom.IonCharge > 0).ToList(); } // Fail if 50% or more of atoms are opposite valence if (oppositeValence.Count >= 0.5 * adjacents.Count) { return false; } else { return true; } } }
/// <summary> /// Get potential reaction tiles. These are non-empty tiles /// that are non-diagonally adjacent to centerTile. /// This also includes tiles that are adjacent to adjacent tiles. /// </summary> /// <param name="centerTile"></param> /// <returns></returns> private void getPotentialReactionTiles(Tile centerTile, IList<Tile> toReturn) { if (!toReturn.Contains(centerTile)) { toReturn.Add(centerTile); } IList<Tile> temp = getNonEmptyAdjacentTiles(centerTile); foreach (Tile t in temp.Where(t => !toReturn.Contains(t))) { // Take adjacencies to all our atoms. getPotentialReactionTiles(t, toReturn); } }
private IList<Tile> getNonEmptyAdjacentTiles(Tile centerTile) { IList<Tile> toReturn = new List<Tile>(); if (centerTile.X > 0) { // left toReturn.Add(this._board[centerTile.X - 1, centerTile.Y]); } if (centerTile.X < BOARD_WIDTH - 1) { // right toReturn.Add(this._board[centerTile.X + 1, centerTile.Y]); } if (centerTile.Y > 0) { toReturn.Add(this._board[centerTile.X, centerTile.Y - 1]); } if (centerTile.Y < BOARD_HEIGHT - 1) { toReturn.Add(this._board[centerTile.X, centerTile.Y + 1]); } return toReturn.Where(t => !t.IsEmpty()).ToList(); }
private void fadeTileIn(Tile t) { // Be yellow. Fade in. t.AtomSprite.ColorOperation = ColorOperation.Add; t.AtomSprite.Red = 1; t.AtomSprite.Green = 1; t.AtomSprite.RedRate = -1; t.AtomSprite.GreenRate = -1; this._fadingInTiles.Add(t); }
private void drawTile(Tile t) { if (!t.IsEmpty()) { Atom a = t.Atom; Sprite s = this.AddSprite("Content/Atoms/" + a.Element + ".png"); s.X = (t.X * 50) - CENTER_OF_BOARD_X; s.Y = (t.Y * -50) + CENTER_OF_BOARD_Y; Text e = this.AddText(a.Element); e.Scale = 16; e.HorizontalAlignment = FlatRedBall.Graphics.HorizontalAlignment.Center; e.X = s.X; e.Y = s.Y; // Do some clean-up. Just incase. if (t.AtomSprite != null) { this.RemoveResource(t.AtomSprite); } if (t.AtomText != null) { this.RemoveResource(t.AtomText); } t.AtomSprite = s; t.AtomText = e; } }