2
\$\begingroup\$

This is my solution to the Codewars problem TV Remote (symbols).

Description

In short, you are given a virtual keyboard with 3 modes between which can be switched by pressing the Shift button on the virtual keyboard.

You have to navigate using only arrow buttons (up, right, down, left) and an OK-button to press the currently selected button. Wraparound is possible.

The challenge is to find the shortest sequence of button presses with which you can write a given string (ignoring optimizations such as prematurely switching Mode).

Questions

  1. Is it correct to make methods which don't rely on the internal state (such as calculateMoves) static? Or is it recommended to effectively duplicate its functionality (for row and col each) and use its internal state?
  2. Since I'm not all too experienced with OOP, did I break any common conventions?

Code

type Coords = { row: number, col: number }

function mod(a: number, b: number): number {
  return ((a % b) + b) % b
}

class TvRemote {
  private static readonly ModeButton = 'aA#'
  private static readonly Mode = [
    // Mode1 = [0]
    [
      'abcde123'.split(''),
      'fghij456'.split(''),
      'klmno789'.split(''),
      'pqrst.@0'.split(''),
      'uvwxyz_/'.split(''),
      [TvRemote.ModeButton, ' ', , , , , , ,]
    ],
    // Mode2 = [1]
    [
      'ABCDE123'.split(''),
      'FGHIJ456'.split(''),
      'KLMNO789'.split(''),
      'PQRST.@0'.split(''),
      'UVWXYZ_/'.split(''),
      [TvRemote.ModeButton, ' ', , , , , , ,]
    ],
    // Mode3 = [2]
    [
      `^~?!\'"()`.split(''),
      '-:;+&%*='.split(''),
      '<>€£$¥¤\\'.split(''),
      '[]{},.@§'.split(''),
      ['#', '¿', '¡', , , , '_', '/'],
      [TvRemote.ModeButton, ' ', , , , , , ,]
    ]
  ]
  private static readonly NumRows = 6
  private static readonly NumCols = 8

  private currRow = 0
  private currCol = 0
  private currModeIndex = 0
  private buttonPresses = 0

  public getButtonPresses (): number {
    return this.buttonPresses
  }

  public write (string: string): void {
    for (const char of string) {
      this.moveToChar(char)
      this.pressButton()
    }
  }

  private currentMode(): string[][] {
    return TvRemote.Mode[this.currModeIndex]
  }

  private currentChar(): string {
    return this.currentMode()[this.currRow][this.currCol]
  }

  private moveToChar (char: string): void {
    this.switchToCorrectMode(char)

    const { row, col } = this.getCoordinates(char)
    const moveRowsBy = TvRemote.calculateMoves(this.currRow, row, TvRemote.NumRows)
    const moveColsBy = TvRemote.calculateMoves(this.currCol, col, TvRemote.NumCols)

    this.move(moveRowsBy, moveColsBy)
  }

  private switchToCorrectMode (char: string): void {
    if (this.existsInCurrentMode(char)) return

    for (let i = 0; i < TvRemote.Mode.length - 1; i++) {
      if (!this.existsInCurrentMode(TvRemote.ModeButton) && TvRemote.Mode.length > 1) {
        throw new Error('Missing button to switch modes')
      }

      this.moveToChar(TvRemote.ModeButton)
      this.pressButton()

      if (this.existsInCurrentMode(char)) {
        return this.moveToChar(char)
      }
    }

    throw new Error(`Character "${char}" does not exist on keyboard`)
  }

  private static calculateMoves (curr: number, target: number, maxNum: number): number {
    // positive: right for x, down for y
    const movePos = (maxNum + target - curr) % maxNum
    const moveNeg = - (maxNum + curr - target) % maxNum

    return Math.abs(movePos) <= Math.abs(moveNeg) ? movePos : moveNeg
  }

  private move (moveRowsBy: number, moveColsBy: number): void {
    this.currRow = mod(this.currRow + moveRowsBy, TvRemote.NumRows)
    this.buttonPresses += Math.abs(moveRowsBy)

    this.currCol = mod(this.currCol + moveColsBy, TvRemote.NumCols)
    this.buttonPresses += Math.abs(moveColsBy)
  }

  private pressButton(): void {
    this.buttonPresses++
    const currChar = this.currentChar()

    console.log(currChar)
    if (currChar === TvRemote.ModeButton) {
      this.currModeIndex = mod(this.currModeIndex + 1, TvRemote.Mode.length)
    }
  }

  private existsInCurrentMode (char: string): boolean {
    const { row , col } = this.getCoordinates(char)
    return row !== -1 && col !== -1
  }

  private getCoordinates (char: string): Coords {
    let col = -1
    let row = this.currentMode().findIndex(rowEntries => {
      col = rowEntries.indexOf(char)
      return col !== -1
    })

    return { row, col }
  }
}

Example output

Calling .write('Too Easy?') should result in 71 button presses. See here for a detailed explanation.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.