diff --git a/code/connect4.html b/code/connect4.html
deleted file mode 100644
index 40db0e1..0000000
--- a/code/connect4.html
+++ /dev/null
@@ -1,131 +0,0 @@
-
-
-
-
- Vier gewinnt
-
-
-
-
-
-
-
-
-
-
-
- Spieler ist am Zug.
-
-
-
-
-
-
-
diff --git a/code/fragments.js b/code/fragments.js
new file mode 100644
index 0000000..9a019e0
--- /dev/null
+++ b/code/fragments.js
@@ -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()
+// }
+// })
+
diff --git a/code/index.js b/code/index.js
index 777456c..55f979c 100644
--- a/code/index.js
+++ b/code/index.js
@@ -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')
\ No newline at end of file
+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)
diff --git a/code/public/connect4.html b/code/public/connect4.html
index f3469a4..f7bb1ea 100644
--- a/code/public/connect4.html
+++ b/code/public/connect4.html
@@ -6,213 +6,24 @@
-
-
+
-
-
-
-
-
+
+
+
+
+
+
's turn
-
diff --git a/code/public/gamelogic.js b/code/public/gamelogic.js
new file mode 100644
index 0000000..c61502b
--- /dev/null
+++ b/code/public/gamelogic.js
@@ -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}
\ No newline at end of file
diff --git a/code/public/lib/suiweb.js b/code/public/lib/suiweb.js
new file mode 100644
index 0000000..c50bf30
--- /dev/null
+++ b/code/public/lib/suiweb.js
@@ -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
+ * =====================================================================
+*/