Berlin Clock Kata
Wix Grow Activity

Before we begin

  • Join #berlin-clock-kata-2023.
  • Prepare single JavaScript file.
  • Prepare to execute your file using node in terminal.
  • Controls:
    • N, right, down - next slide,
    • P, left, top - previous slide,
    • X exit presentation mode.

Berlin Clock

Berlin Clock (cont.)

20230907-220442_screenshot.png

  • Round light blinks to denote odd (on) or even (off) seconds.
  • 1st row denotes five full hours each. 2nd row denotes one hour each.
  • 3rd row eleven lights, which denote five full minutes each.
  • 4th row indicates one full.

Kata

A code kata is an exercise in programming which helps programmers hone their skills through practice and repetition.

karate-girl-karate-kick.gif

Theme of the day

Purely functional style.

Constraints

  • Only const, no let.
  • No re-assignment or mutation.
  • Recursion for iteration.
  • Avoid built-ins.

No re-assignment

array[0] = 1;
object.x = 2;

No mutation

Given this

const a = [1, 2, 3];

None of this

a.push(4);
a.reverse();
a.sort()
a.splice(1, 3);

Destruct & construct

function withFirst(x, array) {
  const [_, ...rest] = array;
  return [x, ...rest];
}

function withKey(object, key, value) {
  return { ...object, [key]: value };
}

Recursion to iterate

No for, while, or .forEach.

a.map(n => n + 1);
a.filter(n => n % 2);
a.reduce((acc, n) => acc + n);

function range(n, m) {
  return new Array(m - n)
    .fill(0)
    .map((_, index) => n + index);
}

Bonus: avoid built-ins

function range(n, m) {
  return n < m ? [n, ...range(n + 1, m)] : [];
}

function tail(xs) {
  const [, ...rest] = xs;
  return rest;
}

function reduce(fn, xs, acc) {
  return xs.length > 0
    ? reduce(fn, tail(xs), fn(acc, xs[0]))
    : acc;
}

function map(fn, xs) {
  return reduce((acc, x) => [...acc, fn(x)], xs, []);
}

Ideas

  • Higher order functions
  • Composition

    join(' ',
         zipWith(call,
    	     [secondsBlock,
    	      minutesBlock,
    	      hoursBlock],
    	     reverse(parseTime(line))))
    
  • Currying

    thread(line,
      parseTime,
      zipWith(call, [secondsBlock,
    		 minutesBlock,
    		 hoursBlock]),
      join(' '))
    

Theme of the day 2

Object Oriented style.

Guidelines

  • No naked operators.
  • Extend built-in objects.
  • Use built-in functionality.
  • Replace built-in control flow.

No naked operators

No.

const x = a + 2;
const y = z % 5;

Yes.

const x = a.add(2);
const y = z.mod(5);

Extend built-ins.

Number.prototype.add = function (number) {
  return this + number;
};
Number.prototype.times = function (fn) {
  const r = [];
  for (let i = 0; i < this; i++) {
    r.push(fn(i));
  }
  return r;
};
(4).times((i) => i * 2) // => [ 0, 2, 4, 6 ]

Use built-in functionality

class Time {
  constructor(h, m, s) {
    this.hours = h;
    this.minutes = m;
    this.seconds = s;
  }

  toString() {
    return [this.hours, this.minutes, this.seconds]
      .map(number => number.toString().padStart(2, '0'))
      .join(':')
  }
}

"Time is " + new Time(12, 5, 1) // => Time is 12:05:01

Replace built-in control flow: if

Number.prototype.lessThan = function (n) {
  return this < n;
}

Boolean.prototype.if = function (yes, no) {
  return this.valueOf() ? yes() : no();
}

(5).lessThan(4).if(() => 'yay', () => 'ney')

Replace built-in control flow: for, while

// instead of for
(5).upTo(10, (i) => console.log(i))
(5).downTo(1, (i) => console.log(i))

// while ?

Million bonus points

Think about the domain.

  • Clock, Lights, Rows of Lights, Hours Rows, Minutes Rows, etc.
  • Light can shine with X or |.
  • Clock can receive time, tell seconds, minutes, and hours to corresponding modules.
  • Modules can choose which lights to turn on or off.

Example

class MinutesBlock {
  row1 = (11).times((i) => (i).mod(3).eq(2).if(() => new PipeLight(), () => new XLight()))
  row2 = (4).times(i => new XLight())

  setMinutes(minutes) {
    this.row1.forEach(light => light.off())
    this.row2.forEach(light => light.off())
    minutes.div(5).times((i) => this.row1[i].on())
    minutes.mod(5).times((i) => this.row2[i].on())
  }

  toString() {
    return this.row1.join('') + ' ' + this.row2.join('')
  }
}

const mb = new MinutesBlock()
mb.setMinutes(33)
mb.toString() // => 'XX|XX|..... XXX.'

Specification

          S H×5  H    M×5         M
00:00:00  . .... .... ........... ....
00:00:01  X .... .... ........... ....
22:23:18  . XXXX XX.. XX|X....... XXX.

Template

require('readline')
  .createInterface({ input: process.stdin })
  .on('line', line => console.log(line + ' => ' + toBerlinClock(line)));

function toBerlinClock(line) {
  return '. .... .... ........... ....';
}
$ echo 10:15:00 | node main.js
10:15:00 => . XX.. .... XX|........ ....
$ node main.js < example.txt
00:00:00 => . .... .... ........... ....
23:59:59 => X XXXX XXX. XX|XX|XX|XX XXXX
18:48:02 => . XXX. XXX. XX|XX|XX|.. XXX.