solved lab

This commit is contained in:
schrom01 2022-12-22 18:31:12 +01:00
parent 59bac579b4
commit e47f9004f0
6 changed files with 632 additions and 331 deletions

View File

@ -1,131 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vier gewinnt</title>
<style>
div {
box-sizing: border-box;
}
.board {
width: 84vw;
margin: auto;
outline: 1px solid black;
}
.board .field {
border: 1px solid black;
width: 12vw;
height: 12vw;
float: left;
}
.board .field:first-child {
display: block;
}
.board .field .piece {
width: 10vw;
height: 10vw;
border-radius: 50%;
margin: 1vw;
}
.board .field .blue {
background-color: blue;
}
.board .field .red {
background-color: red;
}
</style>
<script src="render-sjdon.js"></script>
</head>
<body>
<button class="boardclearbutton" style="margin: 10px;">Neues Spiel</button>
<p>Spieler <a id="actualPlayer"></a> ist am Zug.</p>
<div class="board"></div>
<script>
function showBoard() {
let board = document.querySelector(".board")
board.innerHTML = ''
let element = new Array()
for(row = 0; row < rows; row++) {
for(column = 0; column < columns; column++) {
let attributes = {"id": column.toString() + ":" + row.toString(), "class": "field"}
let children = ["div", {"class": state[row][column] + " piece"}]
element.append("div", attributes, children)
}
}
//renderSJDON(element, board)
}
function clearBoard() {
state = Array(rows).fill('').map(el => Array(columns).fill(''))
switchNextColor()
showBoard();
}
function switchNextColor() {
if(state.nextColor === "red") {
state.nextColor = "blue"
document.getElementById("actualPlayer").innerHTML = "blau"
} else {
state.nextColor = "red"
document.getElementById("actualPlayer").innerHTML = "rot"
}
}
function setField(row, column) {
state[row][column] = state.nextColor
switchNextColor()
showBoard()
}
function setColumn(column) {
let row = rows - 1
const column_number = column
while(row >= 0){
if(state[row][column_number] !== ''){
row--
} else {
setField(row, column_number)
break
}
}
}
const columns = 7
const rows = 6
let state = {}
clearBoard()
const boardClearButton = document.querySelector(".boardclearbutton")
boardClearButton.addEventListener("click", event => {
clearBoard()
})
showBoard()
document.querySelector(".board").addEventListener("click", event => {
let element = event.target
if(element.classList.contains("piece")){
element = element.parentElement
}
let column = Number(element.id.split(":")[0])
setColumn(column)
})
</script>
</body>
</html>

53
code/fragments.js Normal file
View File

@ -0,0 +1,53 @@
import { render } from "./lib/suiweb.js"
let state = {
board: [
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ]
],
next: 'r'
}
let stateSeq = []
// Components
//
const App = () => [Board, {board: state.board}]
const Board = ({board}) => {
let flatBoard = [].concat(...board)
let fields = flatBoard.map((type) => [Field, {type}])
return (
["div", {className: "board"}, ...fields]
)
}
const Field = ({type}) => {
// ...
}
// Show board:
// render [App]
//
function showBoard () {
const app = document.querySelector(".app")
render([App], app)
return app
}
// document.querySelector("button.undo").addEventListener("click", () => {
// if (stateSeq.length > 0) {
// state = stateSeq.pop()
// showBoard()
// }
// })

View File

@ -25,7 +25,7 @@ function guidGenerator() {
}
// Statische Dateien im Verzeichnis public
app.use(express.static('public'))
app.use(express.static(__dirname + "/public"))
// API-Key überprüfen
//
@ -113,7 +113,15 @@ app.use(function(err, req, res, next){
app.use(function(req, res){
res.status(404)
res.send({ error: "not found" })
console.log("sending 404")
console.log(req)
})
app.listen(3000)
console.log('Express started on port 3000')
const fs = require('fs')
const path = __dirname + "/public/styles.css"
console.log(fs.existsSync(path))
console.log(path)

View File

@ -6,213 +6,24 @@
<link rel="stylesheet" href="styles.css">
<script src="connect4-winner.js"></script>
<script src="render-sjdon.js"></script>
<script>
/*
* This solution sould be considered as a proof of concept the code
* definitely needs some cleanup and documentation
*/
let datakey = ''
let state = {
board: [
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ]
],
next: 'blue'
}
const url = "http://localhost:3000/"
const SERVICE = "http://localhost:3000/api/data/c4state?api-key=c4game"
// Initialize game
//
function initGame () {
console.log("initializing game")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(state)
}).then(response => response.json())
.then(data => {
datakey = data.id
})
let board = showBoard()
attachEventHandler(board)
}
// Show board
//
function showBoard () {
console.log("showing board")
let actualPlayerText = document.getElementById("actualPlayer")
if(actualPlayerText){
actualPlayerText.innerText = state.next
}
let board = document.querySelector(".board")
// first remove all fields
while (board.firstChild) { board.removeChild(board.firstChild) }
// your implementation
let element = []
for(row = 0; row < state.board.length; row++) {
for(column = 0; column < state.board[0].length; column++) {
let attributes = {"id": column.toString() + ":" + row.toString(), "class": "field"}
let children = ["div", {"class": state.board[row][column] + " piece"}]
element.push(["div", attributes, children])
}
}
renderSJDON(element, board)
return board
}
// Helper function for DOM manipulation
//
function elt (type, attrs, ...children) {
let node = document.createElement(type)
for (a in attrs) {
node.setAttribute(a, attrs[a])
}
for (let child of children) {
if (typeof child != "string") node.appendChild(child)
else node.appendChild(document.createTextNode(child))
}
return node
}
// Attach event handler to board
//
function attachEventHandler (board) {
console.log("attaching Eventhandler")
// your implementation
board.addEventListener("click", event => {
let field = event.target
if(field.classList.contains("piece")){
field = field.parentNode
}
let column = Number(field.id.split(":")[0])
console.log("calculated column")
setColumn(column)
showBoard()
})
}
function setColumn(column) {
console.log("setting Column")
let row = state.board.length - 1
const column_number = column
while(row >= 0){
if(state.board[row][column_number] !== ''){
row--
} else {
if(state.next === ''){
console.log("not setting Field becuase there is a winner.")
} else {
setField(row, column_number)
}
break
}
}
}
function setField(row, column) {
state.board[row][column] = state.next
showBoard()
if(connect4Winner(state.next, state.board)){
document.getElementById("state-text").innerText = "Player " + state.next + " won!"
state.next = ''
} else {
switchNextColor()
}
}
function switchNextColor() {
if(state.next === "red") {
state.next = "blue"
} else {
state.next = "red"
}
}
// Get current state from server and re-draw board
//
function loadStateFromServer () {
// ...
// your implementation
// ...
console.log("loading State from Server")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'GET',
headers: { 'Content-type': 'application/json' }
}).then(response => response.json())
.then(data => {
state = data
showBoard()
})
}
// Put current state to server
//
function saveStateToServer () {
console.log("saving state To Server")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'PUT',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(state)
}).then(response => response.json())
.then(data => {
state = data
showBoard()
})
}
function loadStateToLocalStorage() {
console.log("loading State from LocalStorage")
if(localStorage.getItem("state")){
state = JSON.parse(localStorage.getItem("state"))
} else {
console.log("no saved State in Localstorage")
}
showBoard()
}
function saveStateFromLocalStorage() {
console.log("saving State to LocalStorage")
localStorage.setItem("state", JSON.stringify(state))
}
function clearLocalStorage() {
console.log("clearing localStorage")
localStorage.clear()
}
</script>
</head>
<body>
<div class="board"></div>
<div class="app"></div>
<div class="controls">
<button onclick="loadStateFromServer()">Load from Server</button>
<button onclick="saveStateToServer()">Save to Server</button>
<button onclick="loadStateToLocalStorage()">Load from LocalStorage</button>
<button onclick="saveStateFromLocalStorage()">Save to LocalStorage</button>
<button onclick="clearLocalStorage()">Clear LocalStorage</button>
<button id="loadFromServer">Load from Server</button>
<button id="saveToServer">Save to Server</button>
<button id="loadFromStorage">Load from LocalStorage</button>
<button id="saveToStorage">Save to LocalStorage</button>
<button id="clearStorage">Clear LocalStorage</button>
<button id ="undo">Undo</button>
<p id="state-text"><a id="actualPlayer"></a>'s turn</p>
</div>
<script>
<script type="module">
import { initGame } from "./gamelogic.js"
initGame()
</script>

224
code/public/gamelogic.js Normal file
View File

@ -0,0 +1,224 @@
/*
* This solution sould be considered as a proof of concept the code
* definitely needs some cleanup and documentation
*/
import { render } from "./lib/suiweb.js"
let datakey = ''
let state = {
board: [
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ],
[ '', '', '', '', '', '', '' ]
],
next: 'blue'
}
let stateSeq = []
const App = () => [Board, {board: state.board}]
const Board = ({board}) => {
let flatBoard = [].concat(...board)
let fields = flatBoard.map((type) => [Field, {type}])
return (
["div", {className: "board"}, ...fields]
)
}
const Field = ({type}) => {
return (
["div", {className: "field"},
["div", {className: ("piece " + type)}]]
)
}
const url = "http://localhost:3000/"
const SERVICE = "http://localhost:3000/api/data/c4state?api-key=c4game"
// Initialize game
//
function initGame () {
console.log("initializing game")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'POST',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(state)
}).then(response => response.json())
.then(data => {
datakey = data.id
})
let board = showBoard()
attachEventHandler(board)
document.getElementById("loadFromServer").addEventListener("click", event => {loadStateFromServer() })
document.getElementById("saveToServer").addEventListener("click", event => {saveStateToServer() })
document.getElementById("loadFromStorage").addEventListener("click", event => {loadStateFromLocalStorage() })
document.getElementById("saveToStorage").addEventListener("click", event => {saveStateToLocalStorage() })
document.getElementById("clearStorage").addEventListener("click", event => {clearLocalStorage() })
document.getElementById("undo").addEventListener("click", event => {undo() })
}
// Show board
function showBoard () {
console.log("showing board")
let actualPlayerText = document.getElementById("actualPlayer")
if(actualPlayerText){
actualPlayerText.innerText = state.next
}
const app = document.querySelector(".app")
render([App], app)
return app
}
// Attach event handler to board
//
function attachEventHandler (board) {
console.log("attaching Eventhandler")
// your implementation
board.addEventListener("click", event => {
let field = event.target
if(field.classList.contains("piece")){
field = field.parentNode
}
let column = Array.prototype.indexOf.call(field.parentNode.children, field) % state.board[1].length
console.log("calculated column", column)
setColumn(column)
showBoard()
})
}
function setColumn(column) {
console.log("setting Column")
let row = state.board.length - 1
const column_number = column
while(row >= 0){
if(state.board[row][column_number] !== ''){
row--
} else {
if(state.next === ''){
console.log("not setting Field becuase there is a winner.")
} else {
setField(row, column_number)
}
break
}
}
}
function setField(row, column) {
stateSeq.push(state)
state = setInObj(state, "board", setInList(state.board, row, setInList(state.board[row], column, state.next)))
//state.board[row][column] = state.next
showBoard()
if(connect4Winner(state.next, state.board)){
document.getElementById("state-text").innerText = "Player " + state.next + " won!"
state = setInObj(state, "next", "")
//state.next = ''
} else {
switchNextColor()
}
}
function switchNextColor() {
if(state.next === "red") {
state = setInObj(state, "next", "blue")
//state.next = "blue"
} else {
state = setInObj(state, "next", "red")
//state.next = "red"
}
}
// Get current state from server and re-draw board
//
function loadStateFromServer () {
// ...
// your implementation
// ...
console.log("loading State from Server")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'GET',
headers: { 'Content-type': 'application/json' }
}).then(response => response.json())
.then(data => {
state = data
stateSeq = []
showBoard()
})
}
// Put current state to server
//
function saveStateToServer () {
console.log("saving state To Server")
fetch(url + "api/data/" + datakey + "?api-key=c4game", {
method: 'PUT',
headers: { 'Content-type': 'application/json' },
body: JSON.stringify(state)
}).then(response => response.json())
.then(data => {
state = data
showBoard()
})
}
function loadStateFromLocalStorage() {
console.log("loading State from LocalStorage")
if(localStorage.getItem("state")){
state = JSON.parse(localStorage.getItem("state"))
stateSeq = []
} else {
console.log("no saved State in Localstorage")
}
showBoard()
}
function saveStateToLocalStorage() {
console.log("saving State to LocalStorage")
localStorage.setItem("state", JSON.stringify(state))
}
function clearLocalStorage() {
console.log("clearing localStorage")
localStorage.clear()
}
function undo() {
if(stateSeq.length > 0) {
console.log("undo")
state = stateSeq.pop()
showBoard()
}
}
function setInList(lst, idx, val) {
const newList = []
lst.forEach(element => {
newList.push(element)
});
newList[idx] = val
return newList
}
function setInObj(obj, attr, val) {
const newObject = {}
Object.keys(obj).forEach(key => {
newObject[key] = obj[key]
})
newObject[attr] = val
return newObject
}
export {initGame}

336
code/public/lib/suiweb.js Normal file
View File

@ -0,0 +1,336 @@
/**
* SuiWeb
* Simple User Interface Tool for Web Exercises
*
* @author bkrt
* @version 0.3.4
* @date 11.12.2021
*
* 0.3.4 - only null or undefined qualify as uninitialized state
* 0.3.3 - parseSJDON rewritten to use createElement
* - flatten children array for props.children in JSX
* 0.3.2 - save and restore of active element improved
* 0.3.1 - parseSJDON rewritten
* 0.3.0 - style property
* 0.2.3 - ES6 modules
* 0.2.2 - component state and hooks
* 0.2.1 - parse SJDON rewritten, function components
* 0.2.0 - render single SJDON structures to DOM
*
* Based on ideas and code from
* Rodrigo Pombo: Build your own React
* https://pomb.us/build-your-own-react/
*
* Thanks to Rodrigo Pombo for a great tutorial and for sharing the
* code of the Didact library. Didact is a much more sophisticated
* re-implementtation of React's basics than the simple SuiWeb.
*/
/* =====================================================================
* SJDON - Conversion
* =====================================================================
*/
// parseSJDON: convert SJDON to createElement calls
//
// note: this function can also help to use React with SJDON syntax
//
// to simplify calls add something like this to your components:
// let s = (data) => reactFromSJDON(data, React.createElement)
//
function parseSJDON ([type, ...rest], create=createElement) {
const isObj = (obj) => typeof(obj)==='object' && !Array.isArray(obj)
const children = rest.filter(item => !isObj(item))
const repr = create(type,
Object.assign({}, ...rest.filter(isObj)),
...children.map(ch => Array.isArray(ch) ? parseSJDON(ch, create) : ch)
)
repr.sjdon = children
return repr
}
// create an element representation
//
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.flat().map(child =>
typeof child === "object"
? child
: createTextElement(child)
),
},
}
}
// create a text element representation
//
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
/* =====================================================================
* Render node tree to DOM
* =====================================================================
*/
// global context
let contextStore = createStore();
// remove children and render new subtree
//
function render(element, container) {
while (container.firstChild) {
container.removeChild(container.lastChild);
}
renderInit(element, container, 0);
}
// render subtree
// and call effects after rendering
//
function renderInit(element, container, n, childIndex) {
contextStore("effects", []);
// save focus and cursor position of active element
let [focusSelector, position] = getFocusInput();
// ** render the element **
renderElem(element, container, n, childIndex);
// restore focus and cursor position of active element
setFocusInput(focusSelector, position);
// run effects
contextStore("effects").forEach(fun => fun());
contextStore("effects", []);
}
// render an element
// - if it is in SJDON form: parse first
// - render function or host component
//
function renderElem(element, container, n, childIndex) {
if (Array.isArray(element)) {
element = parseSJDON(element);
}
if (element.type instanceof Function) {
updateFunctionComponent(element, container, n, childIndex);
} else {
updateHostComponent(element, container, childIndex);
}
}
// function component
// - run function to get child node
// - render child node
//
function updateFunctionComponent(element, container, n, childIndex) {
// save re-render function to context
contextStore("re-render", () => renderInit(element, container, n, n));
let children = element.sjdon ?? element.props.children;
let node = element.type({...element.props, children});
renderElem(node, container, n, childIndex);
}
// host component
// - create dom node
// - assign properties
// - render child nodes
// - add host to dom
//
function updateHostComponent(element, container, childIndex) {
// create DOM node
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type)
// assign the element props
const isProperty = key => key !== "children"
Object.keys(element.props)
.filter(isProperty)
.forEach(name => {
if (name=="style") {
updateStyleAttribute(dom, element.props.style);
} else {
dom[name] = element.props[name];
}
})
// render children
element.props.children.forEach((child, index) => {
renderElem(child, dom, index);
});
if (typeof(childIndex) == 'number') {
// re-render: replace node
container.replaceChild(dom, container.childNodes[childIndex]);
} else {
// add node to container
container.appendChild(dom);
}
}
// update style attribute, value can be:
// - a CSS string: set to style attribute
// - an object: merged to style attribute
// - an array of objects: merged to style attribute
//
function updateStyleAttribute(dom, styles) {
if (typeof(styles)=="string") {
dom.style = styles;
} else if (Array.isArray(styles)) {
Object.assign(dom.style, ...styles);
} else if (typeof(styles)=="object") {
Object.assign(dom.style, styles);
}
}
/* =====================================================================
* Handling state
* =====================================================================
*/
// element state
let stateHooks = createStore();
// state hook
// - access state via id and key
// - return state and update function
//
function useState(id, key, init) {
let idKey = "id:" + id + "-key:" + key;
// prepare render function
let renderFunc = contextStore("re-render");
// define function to update state
function updateValue(updateFun, rerender=true) {
stateHooks(idKey, updateFun(stateHooks(idKey)));
if (rerender) renderFunc();
}
// new state: set initial value
if ([undefined, null].includes(stateHooks(idKey))) {
stateHooks(idKey, init);
}
return [stateHooks(idKey), updateValue];
}
// effect hook
// add function to effects array
//
function useEffect(fun) {
contextStore("effects", [...contextStore("effects"), fun]);
}
// create a key-value-store
// return accessor function
//
function createStore() {
let data = {};
function access(key, ...value) {
if (value.length === 0) {
return data[key];
} else {
data[key] = value[0];
return value[0];
}
}
return access;
}
/* =====================================================================
* Get and set focus and position in certain elements
* Note: this is a quick&dirty solution that probably fails when
* elements are added or removed
* =====================================================================
*/
// create a CSS selector for a given node
//
function getSelectorOf(node) {
let selector = ""
while (node != document.body) {
if (selector != "") selector = ">" + selector
if (node.id) {
selector = "#"+node.id + selector
break
}
let index = Array.from(node.parentNode.children)
.filter(item=>item.tagName==node.tagName)
.findIndex(item=>item==node)
selector = node.tagName + ":nth-of-type(" + (index+1) + ")" + selector
node = node.parentNode
}
return selector
}
// find a selector for the element that has focus and the cursor
// position in the element
//
function getFocusInput() {
const active = document.activeElement;
let sel = active ? getSelectorOf(active) : undefined
let position = active ? active.selectionStart : undefined;
return [sel, position];
}
// set focus to an element in a list of elements matching a
// selector and position cursor in the element
//
function setFocusInput(selector, position) {
if (selector && typeof(selector) == 'string') {
console.log("Sel:"+selector)
let el = document.querySelector(selector);
if (el) el.focus();
if (el && "selectionStart" in el
&& "selectionEnd" in el
&& position !== undefined) {
el.selectionStart = position;
el.selectionEnd = position;
}
}
}
/* =====================================================================
* Module export
* =====================================================================
*/
export { render, createElement, useState, useEffect };
/* =====================================================================
* EOF
* =====================================================================
*/