# Background

My TV remote control has arrow buttons and an `OK` button.

I can use these to move a "cursor" on a logical screen keyboard to type words...

# Keyboard

The screen "keyboard" layout looks like this


  #tvkb {
    width : 400px;
    border: 5px solid gray; border-collapse: collapse;
  }
  #tvkb td {
    color : orange;
    background-color : black;
    text-align : center;
    border: 3px solid gray; border-collapse: collapse;
  }


abcde123
fghij456
klmno789
pqrst.@0
uvwxyz_/
aASP



* `aA` is the SHIFT key. Pressing this key toggles alpha characters between UPPERCASE and lowercase
* `SP` is the space character
* The other blank keys in the bottom row have no function

# Kata task

How many button presses on my remote are required to type the given `words`?

## Notes

* The cursor always starts on the letter `a` (top left)
* The alpha characters are initially lowercase (as shown above)
* Remember to also press `OK` to "accept" each letter
* Take a direct route from one letter to the next
* The cursor does not wrap (e.g. you cannot leave one edge and reappear on the opposite edge)
* Although the blank keys have no function, you may navigate through them if you want to
* Spaces may occur anywhere in the `words` string.
* Do not press the SHIFT key until you need to. For example, with the word `e.Z`, the SHIFT change happens **after** the `.` is pressed (not before)
 
# Example

words = `Code Wars`

* C => `a`-`f`-`k`-`p`-`u`-`aA`-OK-`U`-`P`-`K`-`F`-`A`-`B`-`C`-OK = 14
* o => `C`-`H`-`M`-`R`-`W`-`V`-`U`-`aA`-OK-`SP`-`v`-`q`-`l`-`m`-`n`-`o`-OK = 16
* d => `o`-`j`-`e`-`d`-OK = 4
* e => `d`-`e`-OK = 2
* space => `e`-`d`-`c`-`b`-`g`-`l`-`q`-`v`-`SP`-OK = 9
* W => `SP`-`aA`-OK-`SP`-`V`-`W`-OK = 6
* a => `W`-`V`-`U`-`aA`-OK-`u`-`p`-`k`-`f`-`a`-OK = 10
* r => `a`-`f`-`k`-`p`-`q`-`r`-OK = 6
* s => `r`-`s`-OK = 2

Answer = 14 + 16 + 4 + 2 + 9 + 6 + 10 + 6 + 2 = 69



*Good Luck!
DM.*



Series
* TV Remote
* TV Remote (shift and space)
* TV Remote (wrap)
* TV Remote (symbols)

Difficulty: introductory

Starter code:

def tv_remote(words):
	

Solutions:
import re

KEYBOARD = "abcde123fghij456klmno789pqrst.@0uvwxyz_/* "
MAP      = {c: (i//8, i%8) for i,c in enumerate(KEYBOARD)}


def manhattan(*pts): return 1 + sum( abs(z2-z1) for z1,z2 in zip(*pts))

def toggle(m):
    ups, end = m.group(1), m.group(2)
    off = '*' * bool(end)
    return f'*{ups.lower()}{off}{end}'                # Toggle Shift ON if uppercase presents, and then OFF if lowercase after

def tv_remote(words):
    reWords = re.sub(r'([A-Z][^a-z]*)([a-z]?)', toggle, words)
    return sum( manhattan(MAP[was], MAP[curr]) for was,curr in zip('a'+reWords, reWords))


raw = 'abcde123fghij456klmno789pqrst.@0uvwxyz_/'
layout = {char: divmod(i, 8) for i, char in enumerate(raw)}
layout['SHIFT'] = (5, 0)
layout[' '] = (5,1)

def distance(a, b):
    return abs(a[0] - b[0]) + abs(a[1] - b[1]) + 1


class Keyboard:
    def __init__(self):
        self.total = 0
        self.cap = False
        self.current = (0,0)
        
    def hit_shift(self):
        self.total += distance(self.current, layout['SHIFT'])
        self.cap = not self.cap
        self.current = layout['SHIFT']
    
    def next_letter(self, letter):
        if letter.isupper() and not self.cap:
            self.hit_shift()
        elif letter.islower() and self.cap:
            self.hit_shift()
        
        self.total += distance(self.current, layout[letter.lower()])
        self.current = layout[letter.lower()]
    
    def solve(self, word):
        for char in word:
            self.next_letter(char)


def tv_remote(words):
    k = Keyboard()
    k.solve(words)
    return k.total


import re

def tv_remote(words):
    letters = {c: (x, y)
        for y, row in enumerate((
        "abcde123",
        "fghij456",
        "klmno789",
        "pqrst.@0",
        "uvwxyz_/",
        "⇧ "))
        for x, c in enumerate(row)}
    words = re.sub(r'((?:^|[a-z])[^A-Z]*)([A-Z])', r'\1⇧\2', words)
    words = re.sub(r'([A-Z][^a-z]*)([a-z])', r'\1⇧\2', words)
    words = words.lower()
    return sum(
        abs(letters[c1][0] - letters[c2][0]) +
        abs(letters[c1][1] - letters[c2][1]) + 1
        for c1, c2 in zip("a" + words, words))

keyboard = ("abcde123", "fghij456", "klmno789", "pqrst.@0", "uvwxyz_/", "# ")
D = {c:(i,j) for i,row in enumerate(keyboard) for j,c in enumerate(row)}

def tv_remote(words):
    i = j = up = res = 0
    for c in words:
        if c.isalpha() and c.isupper() != up:
            k, l = D['#']
            res += abs(i-k) + abs(j-l) + 1
            i, j, up = k, l, 1-up
        k, l = D[c.lower()]
        res += abs(i-k) + abs(j-l) + 1
        i, j = k, l
    return res

def tv_remote(words):
    pad = ["abcde123", "fghij456", "klmno789", "pqrst.@0", "uvwxyz_/", "S "]
    index = {c: (x, y) for x, lst in enumerate(pad) for y, c in enumerate(lst)}
    res = 0
    pos = (0, 0)
    caps = False
    for char in words:
        if char.isalpha() and (char.isupper() != caps):
            res += (abs(pos[0] - index["S"][0]) + abs(pos[1] - index["S"][1])) + 1
            pos = index["S"]
            caps = not caps
        
        char = char.lower()
        res += (abs(pos[0] - index[char][0]) + abs(pos[1] - index[char][1])) + 1
        pos = index[char]
    return res

from collections import namedtuple


def tv_remote(word: str):
    remote = (
        'a', 'b', 'c', 'd', 'e', '1', '2', '3',
        'f', 'g', 'h', 'i', 'j', '4', '5', '6',
        'k', 'l', 'm', 'n', 'o', '7', '8', '9',
        'p', 'q', 'r', 's', 't', '.', '@', '0',
        'u', 'v', 'w', 'x', 'y', 'z', '_', '/',
        '', ' '
    )
    Position = namedtuple('Position', 'y x')
    shift, shift_pressed = Position(5, 0), False

    prev = Position(0, 0)
    button_presses = 0
    for letter in word:
        if letter.isalpha() and letter.isupper() != shift_pressed:
            button_presses += abs(prev.y - shift.y) + abs(prev.x - shift.x) + 1
            prev, shift_pressed = shift, not shift_pressed

        cur = Position(*divmod(remote.index(letter.lower()), 8))
        button_presses += abs(prev.y - cur.y) + abs(prev.x - cur.x) + 1
        prev = cur

    return button_presses

def tv_remote(word):
    caps, cases, shift = coords["↑"], (str.isupper, str.islower), 0
    moves, current = 0, (0, 0)
    for char in word:
        target = coords[char.lower()]
        if cases[shift](char):
            moves, current = 1 + moves + distance(current, caps), caps
            shift = 1 - shift
        moves, current = moves + distance(current, target), target
    return moves + len(word)

keyboard = ("abcde123", "fghij456", "klmno789", "pqrst.@0", "uvwxyz_/", "↑ ")
coords = {char: (line.index(char), y) for y, line in enumerate(keyboard) for char in line}

def distance(pos1, pos2):
    return abs(pos2[0] - pos1[0]) + abs(pos2[1] - pos1[1])

def tv_remote(word):
    matrix = [
             ['a', 'b', 'c', 'd', 'e', '1', '2', '3'],
             ['f', 'g', 'h', 'i', 'j', '4', '5', '6'],
             ['k', 'l', 'm', 'n', 'o', '7', '8', '9'],
             ['p', 'q', 'r', 's', 't', '.', '@', '0'],
             ['u', 'v', 'w', 'x', 'y', 'z', '_', '/'],
             ['aA', ' '],
             ]

    actions = ([0, 0],)
    upper_mode = False
    press_ok = 0
    func = lambda x, y: (abs(x[0]-y[0]) + abs(x[1]-y[1]))

    for char in word:
        for i in range(6):
            if char.lower() in matrix[i]:
                if char.isupper() and not upper_mode:
                    actions += ([5, 0],)
                    upper_mode = True
                    press_ok += 1
                    actions += ([i, matrix[i].index(char.lower())],)
                    press_ok += 1
                elif char.isupper() and upper_mode:
                    actions += ([i, matrix[i].index(char.lower())],)
                    press_ok += 1
                elif char.islower() and upper_mode:
                    actions += ([5, 0],)
                    upper_mode = False
                    press_ok += 1
                    actions += ([i, matrix[i].index(char.lower())],)
                    press_ok += 1
                elif char.islower() and not upper_mode:
                    actions += ([i, matrix[i].index(char.lower())],)
                    press_ok += 1
                else:
                    actions += ([i, matrix[i].index(char.lower())],)
                    press_ok += 1

    return sum([func(i[0], i[1]) for i in list(zip(actions, actions[1:]))]) + press_ok

def tv_remote(s):
    d = {y: (i, x) for i, j in enumerate(["abcde123", "fghij456", "klmno789", "pqrst.@0", "uvwxyz_/", "# *!$%^&"]) for x, y in enumerate(j)}
    does = lambda a, b: (abs(d[a][0] - d[b][0]) + (abs(d[a][1] - d[b][1]))) + 1
    status, c = False, 0
    for i, j in zip(s, 'a' + s):
        t, t1 = i.lower(), j.lower()
        if i.isupper():
            if not status : t1, status, c = "#", 1, c + does('#', t1)
        elif i.islower():
            if status : t1, status, c = '#', 0, c + does('#', t1)
        c += does(t1, t)       
    return c

D = {v:(r, c) for r, row in enumerate('''abcde123 fghij456 klmno789 pqrst.@0 uvwxyz_/'''.split() +['  ']) for c, v in enumerate(row)}

def tv_remote(words):
    t, lr, lc, lower = 0, 0, 0, True

    for e in words:
        if e.isalpha() and lower != e.islower():
            t, lr, lc = move(t, lr, lc, 5, 0)
            lower = not lower
        t, lr, lc = move(t, lr, lc, *D[e.lower()])
    return t
    
def move(t, lr, lc, r, c):
    return t + abs(lr - r) + abs(lc - c) + 1, r, c