mirror of
https://github.com/danbulant/introductionToProgramming
synced 2026-05-19 04:18:32 +00:00
week14
This commit is contained in:
parent
f16ba2ddd7
commit
fc80e98eb4
5 changed files with 300 additions and 5 deletions
2
main.typ
2
main.typ
|
|
@ -15,7 +15,7 @@ Collection of solutions to programming exercises as part of Introduction to Prog
|
||||||
)
|
)
|
||||||
|
|
||||||
#{
|
#{
|
||||||
let count = 13;
|
let count = 14;
|
||||||
for week in range(1, count + 1).filter(it => it != 8) {
|
for week in range(1, count + 1).filter(it => it != 8) {
|
||||||
let a = "./week" + str(week) + "/doc.typ"
|
let a = "./week" + str(week) + "/doc.typ"
|
||||||
include a
|
include a
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,12 @@
|
||||||
}:
|
}:
|
||||||
pkgs.mkShell rec {
|
pkgs.mkShell rec {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
jdk
|
(jdk.override { enableJavaFX = true; })
|
||||||
typst
|
typst
|
||||||
tinymist
|
tinymist
|
||||||
nushell
|
nushell
|
||||||
];
|
];
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [];
|
||||||
jre
|
|
||||||
];
|
|
||||||
|
|
||||||
TYPST_FEATURES = "html";
|
TYPST_FEATURES = "html";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
269
week14/Calculator.java
Normal file
269
week14/Calculator.java
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
package week14;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This could have been implemented with a possibly more readable state machine instead of this messy distributed state..
|
||||||
|
|
||||||
|
Value => result of operations, or the first number pressed in before selecting an operation
|
||||||
|
Buffer => currently typed number, right side of operations when activated
|
||||||
|
Number pressed => buffer is !empty / was interacted with
|
||||||
|
Buferred operation => operation selected but not acted on yet
|
||||||
|
Prev buffer => previously used buffer, in case the user wants to repeat operations (by pressing = multiple times)
|
||||||
|
Prev operation => last used operation
|
||||||
|
|
||||||
|
Display => text output (reactive string property)
|
||||||
|
*/
|
||||||
|
public class Calculator extends Application {
|
||||||
|
enum Operation {
|
||||||
|
ADD,
|
||||||
|
SUBSTRACT,
|
||||||
|
MULTIPLY,
|
||||||
|
DIVIDE;
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return switch(this) {
|
||||||
|
case ADD -> "+";
|
||||||
|
case SUBSTRACT -> "-";
|
||||||
|
case MULTIPLY -> "*";
|
||||||
|
case DIVIDE -> "/";
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operation fromString(String op) {
|
||||||
|
return switch(op) {
|
||||||
|
case "+" -> ADD;
|
||||||
|
case "-" -> SUBSTRACT;
|
||||||
|
case "*" -> MULTIPLY;
|
||||||
|
case "/" -> DIVIDE;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public double apply(double left, double right) {
|
||||||
|
return switch(this) {
|
||||||
|
case ADD -> left + right;
|
||||||
|
case SUBSTRACT -> left - right;
|
||||||
|
case MULTIPLY -> left * right;
|
||||||
|
case DIVIDE -> left / right;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double value = 0;
|
||||||
|
double buffer = 0;
|
||||||
|
boolean numberPressed = false;
|
||||||
|
|
||||||
|
Operation bufferredOperation = null;
|
||||||
|
|
||||||
|
double prevBuffer = 0;
|
||||||
|
Operation prevOperation = null;
|
||||||
|
|
||||||
|
StringProperty display = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler after a number was pressed
|
||||||
|
*/
|
||||||
|
void processNumberInput(int num) {
|
||||||
|
assert num >= 0 && num <= 9;
|
||||||
|
resetNumberIfNeeded();
|
||||||
|
numberPressed = true;
|
||||||
|
buffer *= 10;
|
||||||
|
buffer += num;
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler after backspace was pressed
|
||||||
|
*/
|
||||||
|
void backspace() {
|
||||||
|
resetNumberIfNeeded();
|
||||||
|
var num = buffer % 10;
|
||||||
|
buffer -= num;
|
||||||
|
buffer /= 10;
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies operations, used as event handler after = / enter
|
||||||
|
*/
|
||||||
|
void processBuffered() {
|
||||||
|
if(numberPressed && bufferredOperation != null) {
|
||||||
|
value = bufferredOperation.apply(value, buffer);
|
||||||
|
prevOperation = bufferredOperation;
|
||||||
|
bufferredOperation = null;
|
||||||
|
resetBuffer();
|
||||||
|
} else if(prevOperation != null) {
|
||||||
|
value = prevOperation.apply(value, prevBuffer);
|
||||||
|
} else if(bufferredOperation != null) {
|
||||||
|
prevBuffer = value;
|
||||||
|
value = bufferredOperation.apply(value, value);
|
||||||
|
prevOperation = bufferredOperation;
|
||||||
|
bufferredOperation = null;
|
||||||
|
}
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects an operation, computes previous operation if still buffered
|
||||||
|
*/
|
||||||
|
void processOperationInput(Operation op) {
|
||||||
|
assert op != null;
|
||||||
|
if(numberPressed && bufferredOperation != null) {
|
||||||
|
value = bufferredOperation.apply(value, buffer);
|
||||||
|
prevOperation = bufferredOperation;
|
||||||
|
bufferredOperation = null;
|
||||||
|
} else if(numberPressed) {
|
||||||
|
value = buffer;
|
||||||
|
}
|
||||||
|
bufferredOperation = op;
|
||||||
|
resetBuffer();
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all relevant state
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
resetBuffer();
|
||||||
|
value = 0;
|
||||||
|
bufferredOperation = null;
|
||||||
|
prevBuffer = 0;
|
||||||
|
prevOperation = null;
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure value is safe to interact with (enter numbers over)
|
||||||
|
* resets to 0 if NaN or infinite
|
||||||
|
*/
|
||||||
|
void resetNumberIfNeeded() {
|
||||||
|
if(!Double.isFinite(value)) value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDisplay() {
|
||||||
|
if(numberPressed) display.set(Double.toString(buffer));
|
||||||
|
else if(Double.isNaN(value)) display.set("ERROR");
|
||||||
|
else display.set(Double.toString(value));
|
||||||
|
|
||||||
|
// System.out.format("value %s buffer %s numberPressed %s buferredOperation %s prevBuffer %s prevOperation %s\n", value, buffer, numberPressed, buferredOperation, prevBuffer, prevOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetBuffer() {
|
||||||
|
prevBuffer = buffer;
|
||||||
|
buffer = 0;
|
||||||
|
numberPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the ui
|
||||||
|
*
|
||||||
|
* VALUE/BUFFER/ERROR
|
||||||
|
*
|
||||||
|
* 7 8 9 + ⌫
|
||||||
|
* 4 5 6 -
|
||||||
|
* 1 2 3 *
|
||||||
|
* 0 / =
|
||||||
|
*/
|
||||||
|
public void start(Stage primaryStage) {
|
||||||
|
updateDisplay();
|
||||||
|
var output = new Label();
|
||||||
|
output.setStyle("-fx-font-size: 30px; -fx-text-alignment: right;");
|
||||||
|
output.textProperty().bind(display);
|
||||||
|
|
||||||
|
var numberPanel = new GridPane();
|
||||||
|
|
||||||
|
for(var i = 0; i < 9; i++) {
|
||||||
|
var button = new Button(Integer.toString(i + 1));
|
||||||
|
var index = i;
|
||||||
|
button.setOnMouseClicked(e -> {
|
||||||
|
processNumberInput(index + 1);
|
||||||
|
});
|
||||||
|
numberPanel.add(button, i % 3, 2-(i / 3));
|
||||||
|
}
|
||||||
|
var zero = new Button("0");
|
||||||
|
zero.setOnMouseClicked(e -> {
|
||||||
|
processNumberInput(0);
|
||||||
|
});
|
||||||
|
numberPanel.add(zero, 1, 3);
|
||||||
|
|
||||||
|
var controlPanel = new GridPane();
|
||||||
|
|
||||||
|
var values = Operation.values();
|
||||||
|
for(var i = 0; i < values.length; i++) {
|
||||||
|
var op = values[i];
|
||||||
|
var button = new Button(op.toString());
|
||||||
|
button.setOnMouseClicked(e -> {
|
||||||
|
processOperationInput(op);
|
||||||
|
});
|
||||||
|
controlPanel.add(button, 0, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
var backspace = new Button("⌫");
|
||||||
|
backspace.setOnMouseClicked(e -> backspace());
|
||||||
|
controlPanel.add(backspace, 1, 0);
|
||||||
|
var equals = new Button("=");
|
||||||
|
equals.setOnMouseClicked(e -> processBuffered());
|
||||||
|
controlPanel.add(equals, 1, 3);
|
||||||
|
|
||||||
|
var hbox = new HBox();
|
||||||
|
hbox.setAlignment(Pos.CENTER);
|
||||||
|
hbox.getChildren().add(numberPanel);
|
||||||
|
hbox.getChildren().add(controlPanel);
|
||||||
|
|
||||||
|
var vbox = new VBox();
|
||||||
|
vbox.setAlignment(Pos.TOP_CENTER);
|
||||||
|
vbox.getChildren().add(output);
|
||||||
|
vbox.getChildren().add(hbox);
|
||||||
|
VBox.setVgrow(hbox, Priority.SOMETIMES);
|
||||||
|
|
||||||
|
Scene scene = new Scene(vbox, 300, 250);
|
||||||
|
scene.setOnKeyPressed(ev -> {
|
||||||
|
final var ch = ev.getText();
|
||||||
|
try {
|
||||||
|
var num = Integer.parseInt(ch);
|
||||||
|
processNumberInput(num);
|
||||||
|
return;
|
||||||
|
} catch(NumberFormatException e) {}
|
||||||
|
var op = Operation.fromString(ch);
|
||||||
|
if(op != null) {
|
||||||
|
processOperationInput(op);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ev.getCode() == KeyCode.BACK_SPACE) {
|
||||||
|
backspace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ev.getCode() == KeyCode.ESCAPE) {
|
||||||
|
reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(ev.getCode() == KeyCode.ENTER || ev.getCode() == KeyCode.EQUALS) {
|
||||||
|
processBuffered();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
primaryStage.setTitle("Calculator");
|
||||||
|
primaryStage.setScene(scene);
|
||||||
|
primaryStage.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
week14/common
Symbolic link
1
week14/common
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../common
|
||||||
27
week14/doc.typ
Normal file
27
week14/doc.typ
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#import "./common/common.typ" : *
|
||||||
|
|
||||||
|
#show: template
|
||||||
|
|
||||||
|
= Week 14
|
||||||
|
|
||||||
|
Write a graphical user interface for a simple calculator application.
|
||||||
|
|
||||||
|
Here are some steps to get started:
|
||||||
|
|
||||||
|
- Add a label to show the currently entered number.
|
||||||
|
- Use horizontal and vertical boxes, or a grid, to construct a layout of buttons numbered from 0 to 9.
|
||||||
|
- Add buttons for addition, subtraction, multiplication, and division.
|
||||||
|
|
||||||
|
The calculator should work as follows: You press “5”, it shows up in the display, then you press “+”, and then you press “7” and it becomes “12”.
|
||||||
|
|
||||||
|
Hint: Experiment with the calculator on your system to discover how it works.
|
||||||
|
|
||||||
|
Here are some optional extensions to consider:
|
||||||
|
|
||||||
|
- Style the label to make the result bigger.
|
||||||
|
- Style the digit buttons so they are bigger.
|
||||||
|
- Add an event handler such that user can press the keys 0 to 9 on the keyboard with the same effect as using the buttons.
|
||||||
|
- Add a try-catch block to detect division by zero and show an appropriate alert message.
|
||||||
|
- Add an event handler such that the escape key resets the calculator.
|
||||||
|
|
||||||
|
#embedClass(name: "Calculator")
|
||||||
Loading…
Reference in a new issue