Code Examples for Reading 20
Download
You can also download a ZIP file containing this code.
draw.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Draw!</title>
<style>
#canvas {
border: 1px solid black;
}
div {
margin: 5px;
}
</style>
</head>
<body>
<div>
Color: <input type="text" id="colorTextbox" value="green">
</div>
<div>
Shape: <select id="shapeDropdown">
<option value="circle" selected>circle</option>
<option value="square">square</option>
</select>
<input type="checkbox" id="filledCheckbox" checked="true"> filled
</div>
<div>
Size: <input type="number" id="sizeBox" value="10" min="1" max="50" maxlength="2">
<input type="range" id="sizeSlider" value="10" min="1" max="50">
</div>
<div>
<button id="drawButton">Draw Randomly</button>
<button id="clearButton">Clear</button>
</div>
<div>
<canvas id="canvas" width="256" height="256">
</canvas>
</div>
<script src="dist/draw-bundle.js"></script>
</body>
</html>
draw.ts
/* Copyright (c) 2021 MIT 6.031 course staff, all rights reserved.
* Redistribution of original or derived work requires permission of course staff.
*/
// this code is loaded into draw.html
const drawButton: HTMLButtonElement = document.getElementById('drawButton') as HTMLButtonElement;
const clearButton: HTMLButtonElement = document.getElementById('clearButton') as HTMLButtonElement;
const canvasElement: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
const sizeBox: HTMLInputElement = document.getElementById('sizeBox') as HTMLInputElement;
const sizeSlider: HTMLInputElement = document.getElementById('sizeSlider') as HTMLInputElement;
drawButton.addEventListener('click', (event:MouseEvent) => {
drawShape(canvasElement.width * Math.random(), canvasElement.height * Math.random());
// for (let i = 0; i < 1000*1000; ++i) {
// drawShape(canvasElement.width * Math.random(), canvasElement.height * Math.random());
// }
});
clearButton.addEventListener('click', clear);
// synchronize the size textbox and slider
sizeBox.addEventListener('input', () => {
sizeSlider.value = sizeBox.value;
});
sizeSlider.addEventListener('input', () => {
sizeBox.value = sizeSlider.value;
});
/**
* @returns color shown in the color textbox
*/
function getColor():string {
return (document.getElementById('colorTextbox') as HTMLInputElement).value;
}
/**
* @returns size shown in the size textbox
*/
function getSize():number {
return parseInt((document.getElementById('sizeBox') as HTMLInputElement).value);
}
/**
* Draw a square filled with a random color.
* @param x x position of center of box centers
* @param y y position of center of box centers
*/
function drawShape(x: number, y: number):void {
const canvas = canvasElement.getContext('2d');
if ( ! canvas ) throw new Error('unable to get canvas drawing context');
// save original context settings before we translate and change colors
canvas.save();
// move origin to (x,y)
canvas.translate(x, y);
// draw the outer outline box centered on the origin (which is now x,y)
canvas.strokeStyle = 'black';
const size = getSize();
canvas.lineWidth = 2;
canvas.strokeRect( -size/2, -size/2, size, size);
// fill with a random color
canvas.fillStyle = getColor();
canvas.fillRect( -size/2, -size/2, size, size);
// reset the origin and styles back to defaults
canvas.restore();
}
/**
* Clear the drawing area, filling it with white again.
*/
function clear() {
const context = canvasElement.getContext('2d');
if ( ! context ) throw new Error('unable to get canvas drawing context');
context.save();
context.fillStyle = 'white';
context.fillRect(0, 0, canvasElement.width, canvasElement.height);
context.restore();
}
Counter.ts
// Type of Counter's listener callback
export type NumberListener = (numberReached:bigint) => void;
/** A mutable incrementing counter that calls its listeners each time it
* reaches a new number. */
export class Counter {
private _value: bigint = 0n;
private listeners: Array<NumberListener> = [];
// Abstraction function
// AF(_value, listeners) = a counter currently at `_value`
// that sends events to the `listeners` whenever it changes
// Rep invariant
// true
/** Make a counter initially set to zero. */
public constructor() {
}
/** @returns the value of this counter. */
public get value():bigint {
return this._value;
}
/** Increment this counter. */
public increment():void {
++this._value;
this.callListeners();
}
/** Modifies this counter by adding a listener.
* @param listener called by this counter when it changes. */
public addEventListener(listener: NumberListener):void {
this.listeners.push(listener);
}
/** Modifies this counter by removing a listener.
* @param listener will no longer be called by this counter. */
public removeEventListener(listener: NumberListener):void {
// search backwards through the array,
// so we can remove all matches to `listeners`
// without having to adjust the index
for (let i = this.listeners.length-1; i >= 0; --i) {
if (this.listeners[i] === listener) {
this.listeners.splice(i, 1);
}
}
}
// Call all listeners to notify them about a new number
private callListeners():void {
// iterate over a copy of listeners so that we are re-entrant
// (i.e. a listener may call add/removeNumberListener,
// which would mutate the array in the midst of iteration)
for (const listener of [...this.listeners]) {
listener(this._value);
}
}
}
/**
* Example client code, trying out a Counter with a couple listeners.
*/
function main() {
const counter = new Counter();
// listen for multiples of 113
counter.addEventListener((value) => {
if (value % 113n === 0n) {
console.log(value, "is a multiple of 113");
}
});
// listen for 10000, then exit the process
counter.addEventListener((value) => {
if (value === 10000n) {
process.exit(0);
}
});
// crank the counter
while (true) {
counter.increment();
}
}
if (require.main === module) {
main();
}