1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<html>
<head>
<style>
#grid-canvas {
border-collapse: collapse;
}
#grid-canvas td {
width: 20px;
height: 20px;
padding: 0;
border: 1px solid black;
}
</style>
<script>
const GRID_WIDTH = 20
const GRID_HEIGHT = 20
// The <input type='color'> used to select the drawing color
let colorInput
// The <table> used to display the pixel art
let grid
// The WebSocket
let socket
// Rectangles are drawn by clicking and dragging.
// These store the row and column of the cell that was clicked.
let startRow
let startCol
// Sends a `set-color` message with the current color.
// Called when the page loads and whenever `colorInput` changes.
function sendColor() {
const message = {type: 'set-color', color: colorInput.value}
socket.send(JSON.stringify(message))
}
// Sends a `draw` message to create a rectangle
// from `startRow` and `startCol` to the given row and column
function sendRectangle(row, col) {
const message = {
type: 'draw',
// Using Math.min() and Math.abs()
// since the rectangle could have been drawn in any direction
row: Math.min(startRow, row),
col: Math.min(startCol, col),
rows: Math.abs(startRow - row) + 1,
cols: Math.abs(startCol - col) + 1
}
socket.send(JSON.stringify(message))
}
// Adds the table cell elements to `grid` and attaches
// `mousedown` and `mouseup` event handlers to them
function makeGrid() {
// Build a grid of <tr> and <td> elements, like in Minesweeper
for (let row = 0; row < GRID_HEIGHT; row++) {
const gridRow = document.createElement('tr')
for (let col = 0; col < GRID_WIDTH; col++) {
const cell = document.createElement('td')
// Handles the click of a click-and-drag
cell.onmousedown = () => {
startRow = row
startCol = col
}
// Handles the release of a click-and-drag
cell.onmouseup = () => sendRectangle(row, col)
gridRow.append(cell)
}
grid.append(gridRow)
}
}
// Handles a `draw` message from the server.
// `{color, ..., cols}` unpacks the message's fields.
function draw({color, row, col, rows, cols}) {
// For each cell in the rectangle, update its background color
for (let i = 0; i < rows; i++) {
const gridRow = grid.children[row + i]
for (let j = 0; j < cols; j++) {
const gridCell = gridRow.children[col + j]
gridCell.style.background = color
}
}
}
// We need to run code that accesses HTML elements after
// the rest of the HTML is parsed (signaled by the `load` event)
window.onload = () => {
// Retrieve HTML elements
colorInput = document.getElementById('color')
grid = document.getElementById('grid-canvas')
colorInput.onchange = sendColor
makeGrid()
// Create a WebSocket connection
socket = new WebSocket('ws://localhost')
// As soon as socket opens, send the initial color (black)
socket.onopen = sendColor
// Handles `draw` messages from the server.
// `event.data` is the JSON string received.
socket.onmessage = event => draw(JSON.parse(event.data))
}
</script>
</head>
<body>
<input type='color' id='color' />
<table id='grid-canvas'></table>
</body>
</html>