bgneal@3: // Copyright (C) 2012 by Brian Neal.
bgneal@3: // This file is part of Cpp-Enigma, the Enigma Machine simulation.
bgneal@3: // Cpp-Enigma is released under the MIT License (see License.txt).
bgneal@3: //
bgneal@3: // plugboard.cpp - This is the implementation file for the plugboard class.
bgneal@3: 
bgneal@3: #include <algorithm>
bgneal@3: #include <set>
bgneal@3: #include <sstream>
bgneal@3: #include <utility>
bgneal@3: #include "plugboard.h"
bgneal@3: 
bgneal@3: using namespace enigma;
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: namespace
bgneal@3: {
bgneal@3:    // Returns a wiring map with "straight-through" mapping, where every input
bgneal@3:    // pin 'i' is wired to the output pin 'i':
bgneal@3: 
bgneal@3:    alpha_int_array straight_through_mapping()
bgneal@3:    {
bgneal@3:       alpha_int_array result;
bgneal@3:       for (alpha_int_array::size_type i = 0; i < result.size(); ++i)
bgneal@3:       {
bgneal@3:          result[i] = i;
bgneal@3:       }
bgneal@3:       return result;
bgneal@3:    }
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: plugboard::plugboard()
bgneal@3:  : wiring_map(straight_through_mapping())
bgneal@3: {
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: plugboard::plugboard(const pair_vector& pairs)
bgneal@3:  : wiring_map(straight_through_mapping())
bgneal@3: {
bgneal@3:    construct_wiring(pairs);
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: plugboard::plugboard(const std::string& settings)
bgneal@3:  : wiring_map(straight_through_mapping())
bgneal@3: {
bgneal@3:    if (settings.empty())
bgneal@3:    {
bgneal@3:       return;
bgneal@3:    }
bgneal@3: 
bgneal@3:    pair_vector pairs;
bgneal@3: 
bgneal@3:    // detect which syntax is being used
bgneal@3:    if (settings.find('/') == std::string::npos)
bgneal@3:    {
bgneal@3:       // Assume Heer (army) syntax
bgneal@3: 
bgneal@3:       std::istringstream iss(settings);
bgneal@3:       std::string s;
bgneal@3:       while (iss >> s)
bgneal@3:       {
bgneal@3:          if (s.size() != 2)
bgneal@3:          {
bgneal@3:             throw plugboard_error("invalid settings string");
bgneal@3:          }
bgneal@3:          const int m = std::toupper(s[0]) - 'A';
bgneal@3:          const int n = std::toupper(s[1]) - 'A';
bgneal@3: 
bgneal@3:          pairs.push_back(std::make_pair(m, n));
bgneal@3:       }
bgneal@3:    }
bgneal@3:    else
bgneal@3:    {
bgneal@3:       // Assume Kriegsmarine (navy) syntax
bgneal@3: 
bgneal@3:       std::istringstream iss(settings);
bgneal@3:       std::string s;
bgneal@3:       while (iss >> s)
bgneal@3:       {
bgneal@3:          const std::size_t x = s.find('/');
bgneal@3:          if (x == std::string::npos || x == s.size() - 1)
bgneal@3:          {
bgneal@3:             throw plugboard_error("invalid settings string");
bgneal@3:          }
bgneal@3: 
bgneal@3:          int m;
bgneal@3:          int n;
bgneal@3:          std::istringstream mss(s.substr(0, x));
bgneal@3:          std::istringstream nss(s.substr(x + 1));
bgneal@3: 
bgneal@3:          if ((mss >> m) && (nss >> n))
bgneal@3:          {
bgneal@3:             pairs.push_back(std::make_pair(m - 1, n - 1));
bgneal@3:          }
bgneal@3:          else
bgneal@3:          {
bgneal@3:             throw plugboard_error("invalid settings string");
bgneal@3:          }
bgneal@3:       }
bgneal@3:    }
bgneal@3: 
bgneal@3:    construct_wiring(pairs);
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: plugboard::pair_vector plugboard::get_pairs() const
bgneal@3: {
bgneal@3:    std::set<std::pair<int, int>> pair_set;
bgneal@3:    for (int i = 0; i < 26; ++i)
bgneal@3:    {
bgneal@3:       const int j = wiring_map[i];
bgneal@3:       if (i < j)
bgneal@3:       {
bgneal@3:          pair_set.insert(std::make_pair(i, j));
bgneal@3:       }
bgneal@3:    }
bgneal@3: 
bgneal@3:    return pair_vector(pair_set.begin(), pair_set.end());
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: std::string plugboard::army_str() const
bgneal@3: {
bgneal@3:    const auto pairs = get_pairs();
bgneal@3: 
bgneal@3:    std::string s;
bgneal@3: 
bgneal@6:    for (const auto& p : pairs)
bgneal@3:    {
bgneal@3:       s += static_cast<char>(p.first + 'A');
bgneal@3:       s += static_cast<char>(p.second + 'A');
bgneal@3:       s += ' ';
bgneal@3:    }
bgneal@6: 
bgneal@6:    if (!s.empty())
bgneal@6:    {
bgneal@6:       s.erase(s.size() - 1);     // erase trailing space
bgneal@6:    }
bgneal@3:    return s;
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: std::string plugboard::navy_str() const
bgneal@3: {
bgneal@3:    const auto pairs = get_pairs();
bgneal@3: 
bgneal@3:    std::ostringstream os;
bgneal@6:    for (const auto& p : pairs)
bgneal@3:    {
bgneal@3:       os << (p.first + 1) << '/' << (p.second + 1) << ' ';
bgneal@3:    }
bgneal@3: 
bgneal@3:    std::string s(os.str());
bgneal@6:    if (!s.empty())
bgneal@6:    {
bgneal@6:       s.erase(s.size() - 1);     // erase trailing space
bgneal@6:    }
bgneal@3:    return s;
bgneal@3: }
bgneal@3: 
bgneal@3: ////////////////////////////////////////////////////////////////////////////////
bgneal@3: 
bgneal@3: void plugboard::construct_wiring(const pair_vector& pairs)
bgneal@3: {
bgneal@3:    if (pairs.size() > max_pairs)
bgneal@3:    {
bgneal@3:       throw plugboard_error("Too many pairs");
bgneal@3:    }
bgneal@3: 
bgneal@3:    // range check the wiring & ensure a path appears at most once
bgneal@3:    // (the double braces were added because gcc 4.6.3 emits a warning without
bgneal@3:    // them with -std=c++0x -Wall -Wextra -pedantic)
bgneal@3:    alpha_int_array counts = {{ 0 }};
bgneal@3:    for (const auto& p : pairs)
bgneal@3:    {
bgneal@3:       if (p.first < 0 || p.second < 0 || p.first >= 26 || p.second >= 26)
bgneal@3:       {
bgneal@3:          throw plugboard_error("invalid wiring pair");
bgneal@3:       }
bgneal@3:       ++counts[p.first];
bgneal@3:       ++counts[p.second];
bgneal@3:    }
bgneal@3: 
bgneal@3:    if (std::find_if(counts.begin(),
bgneal@3:                     counts.end(),
bgneal@3:                     [](int n) { return n > 1; }) != counts.end())
bgneal@3:    {
bgneal@3:       throw plugboard_error("duplicate connection");
bgneal@3:    }
bgneal@3: 
bgneal@3:    // all checks pass if we made it this far; make the connections
bgneal@3: 
bgneal@3:    for (auto& p : pairs)
bgneal@3:    {
bgneal@3:       wiring_map[p.first] = p.second;
bgneal@3:       wiring_map[p.second] = p.first;
bgneal@3:    }
bgneal@3: }