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="sizeTextbox" value="10" min="1" max="50" maxlength="2">
<input type="range" id="sizeSlider" value="10" min="1" max="50">
</div>
<div>
<button id="drawRandomlyButton">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 drawRandomlyButton: HTMLButtonElement = document.getElementById('drawRandomlyButton') as HTMLButtonElement;
const clearButton: HTMLButtonElement = document.getElementById('clearButton') as HTMLButtonElement;
const canvasElement: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
const colorTextbox: HTMLInputElement = document.getElementById('colorTextbox') as HTMLInputElement;
const shapeDropdown: HTMLSelectElement = document.getElementById('shapeDropdown') as HTMLSelectElement;
const filledCheckbox: HTMLInputElement = document.getElementById('filledCheckbox') as HTMLInputElement;
const sizeTextbox: HTMLInputElement = document.getElementById('sizeTextbox') as HTMLInputElement;
const sizeSlider: HTMLInputElement = document.getElementById('sizeSlider') as HTMLInputElement;
canvasElement.addEventListener('click', (event:MouseEvent) => {
drawShape(event.offsetX, event.offsetY);
});
drawRandomlyButton.addEventListener('click', () => {
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
sizeTextbox.addEventListener('input', () => {
sizeSlider.value = sizeTextbox.value;
});
sizeSlider.addEventListener('input', () => {
sizeTextbox.value = sizeSlider.value;
});
/**
* @returns color shown in the color textbox
*/
function getColor():string {
return colorTextbox.value;
}
/**
* @returns size shown in the size textbox
*/
function getSize():number {
return parseInt(sizeTextbox.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);
// describe the shape, either a circle or a rectangle, centered on the origin
const size = getSize();
canvas.beginPath();
if (shapeDropdown.value === 'circle') {
canvas.arc(0, 0, size/2, 0, 2 * Math.PI);
} else {
canvas.rect(-size/2, -size/2, size, size)
}
// stroke the outline of the shape
canvas.strokeStyle = 'black';
canvas.lineWidth = 2;
canvas.stroke();
if (filledCheckbox.checked) {
// fill with a color
canvas.fillStyle = getColor();
canvas.fill();
}
// 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
/** 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<(numberReached:bigint) => void> = [];
// 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: (numberReached:bigint) => void):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: (numberReached:bigint) => void):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();
}