From a29eedb6e7717c025c6f9848b5e339bb3a02142d Mon Sep 17 00:00:00 2001 From: xAlessandroC Date: Wed, 4 Oct 2023 19:51:43 +0200 Subject: [PATCH 1/3] CI/CD - Disabled windows job --- .github/workflows/multi-platform-workflow.yml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/multi-platform-workflow.yml b/.github/workflows/multi-platform-workflow.yml index 5cf6b1b..942c681 100644 --- a/.github/workflows/multi-platform-workflow.yml +++ b/.github/workflows/multi-platform-workflow.yml @@ -35,25 +35,25 @@ jobs: - name: Run Flask server tests run: python -m unittest test/app_test.py - windows-support: - runs-on: windows-latest - steps: - - name: Set up Python 3.10.9 - uses: actions/setup-python@v4.7.0 - with: - python-version: 3.10.9 - - name: Print the version of Python # DEBUG - run: python --version - - name: Checkout the repository - uses: actions/checkout@v3.5.3 - - name: Print repo structure # DEBUG - run: dir && dir gameoflife && dir test - - name: Install dependencies - run: pip install -r ./requirements.txt - - name: Print dependencies # DEBUG - run: pip freeze - - name: Run Flask server tests - run: python -m unittest test/app_test.py + # windows-support: + # runs-on: windows-latest + # steps: + # - name: Set up Python 3.10.9 + # uses: actions/setup-python@v4.7.0 + # with: + # python-version: 3.10.9 + # - name: Print the version of Python # DEBUG + # run: python --version + # - name: Checkout the repository + # uses: actions/checkout@v3.5.3 + # - name: Print repo structure # DEBUG + # run: dir && dir gameoflife && dir test + # - name: Install dependencies + # run: pip install -r ./requirements.txt + # - name: Print dependencies # DEBUG + # run: pip freeze + # - name: Run Flask server tests + # run: python -m unittest test/app_test.py dockerize: runs-on: ubuntu-22.04 From 4db6c923501e35ccef508f42cca108c7e18e550d Mon Sep 17 00:00:00 2001 From: xAlessandroC Date: Wed, 4 Oct 2023 19:52:03 +0200 Subject: [PATCH 2/3] core - Add game of life game core and flask setup --- Dockerfile | 3 +- gameoflife/app.py | 6 +- gameoflife/static/js/cell.js | 8 ++ gameoflife/static/js/grid.js | 58 +++++++++ gameoflife/static/js/logic.js | 86 +++++++++++++ gameoflife/static/js/normalLogic.js | 48 +++++++ gameoflife/static/js/pattern.js | 111 ++++++++++++++++ gameoflife/static/js/quadtree.js | 192 ++++++++++++++++++++++++++++ gameoflife/static/js/rectangle.js | 8 ++ gameoflife/static/js/sketch.js | 130 +++++++++++++++++++ gameoflife/static/js/utility.js | 53 ++++++++ gameoflife/templates/main.html | 40 ++++++ 12 files changed, 740 insertions(+), 3 deletions(-) create mode 100644 gameoflife/static/js/cell.js create mode 100644 gameoflife/static/js/grid.js create mode 100644 gameoflife/static/js/logic.js create mode 100644 gameoflife/static/js/normalLogic.js create mode 100644 gameoflife/static/js/pattern.js create mode 100644 gameoflife/static/js/quadtree.js create mode 100644 gameoflife/static/js/rectangle.js create mode 100644 gameoflife/static/js/sketch.js create mode 100644 gameoflife/static/js/utility.js create mode 100644 gameoflife/templates/main.html diff --git a/Dockerfile b/Dockerfile index 27c4f06..8124f4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,5 +16,6 @@ RUN pip install -r requirements.txt # Copy app source COPY . . +WORKDIR /app/gameoflife EXPOSE 5000 -CMD [ "flask", "--app", "game-of-life/app.py", "--debug", "run", "--host", "0.0.0.0", "--port", "5000"] \ No newline at end of file +CMD [ "flask", "--app", "app", "--debug", "run", "--host", "0.0.0.0", "--port", "5000"] \ No newline at end of file diff --git a/gameoflife/app.py b/gameoflife/app.py index 70f8b31..608e780 100644 --- a/gameoflife/app.py +++ b/gameoflife/app.py @@ -1,7 +1,9 @@ -from flask import Flask +import os +from flask import Flask, render_template + app = Flask(__name__) @app.route("/") def hello_world(): - return "

Conway's Game of Life

" \ No newline at end of file + return render_template('main.html') \ No newline at end of file diff --git a/gameoflife/static/js/cell.js b/gameoflife/static/js/cell.js new file mode 100644 index 0000000..a40d9ed --- /dev/null +++ b/gameoflife/static/js/cell.js @@ -0,0 +1,8 @@ +class Cell{ + constructor(x,y,alive){ + this.x = x; + this.y = y; + this.alive = alive; + } + +} diff --git a/gameoflife/static/js/grid.js b/gameoflife/static/js/grid.js new file mode 100644 index 0000000..f4078a9 --- /dev/null +++ b/gameoflife/static/js/grid.js @@ -0,0 +1,58 @@ +class GridQT{ + //starting point, width, height + constructor(rectangle){ + this.rectangle=rectangle; + + this.matrix = []; + for(var i=this.rectangle.start_col; i=0 && j>=0 && + i0){ + newSC=newSC-1 + newW=newW+1 + } + if(rect.start_row>0){ + newSR=newSR-1 + newH=newH+1 + } + if(grid.start_col+grid.width-1>rect.start_col+rect.width-1) + newW=newW+1 + if(grid.start_row+grid.height-1>rect.start_row+rect.height-1) + newH=newH+1 + + var rectangle=new Rectangle(newSC,newSR,newW,newH) + + var newMatrix=[]; + for(var i=rectangle.start_col; i3) { + newMatrix[i][j]=new Cell(i,j,false); + settato=true; + }else{ + newMatrix[i][j]=new Cell(i,j,true); + } + }else { + if(numberAlive==3) { + newMatrix[i][j]=new Cell(i,j,true); + settato=true; + }else{ + newMatrix[i][j]=new Cell(i,j,false); + } + } + } + } + + result.push([newMatrix,rectangle]) +} + +function nextGen(){ + regions = quadtree.query() + var result = [] + regions.forEach(function(e){ + nextRectGen(e,result) + }); + + result.forEach(function(e){ + grid.substitute(e[0],e[1],grid) + }); +} diff --git a/gameoflife/static/js/normalLogic.js b/gameoflife/static/js/normalLogic.js new file mode 100644 index 0000000..e69124a --- /dev/null +++ b/gameoflife/static/js/normalLogic.js @@ -0,0 +1,48 @@ +function normalGetNeighbourAlive(col,row){ + var result=0; + var i,j; + for( i=col-1;i<=col+1;i++) { + for( j=row-1;j<=row+1;j++) { + if(i>=0 && j>=0 && i3) { + newMatrix[i][j]=new Cell(i,j,false); + settato=true; + }else{ + newMatrix[i][j]=new Cell(i,j,true); + } + }else { + if(numberAlive==3) { + newMatrix[i][j]=new Cell(i,j,true); + settato=true; + }else{ + newMatrix[i][j]=new Cell(i,j,false); + } + } + } + } + + grid.matrix=newMatrix; +} diff --git a/gameoflife/static/js/pattern.js b/gameoflife/static/js/pattern.js new file mode 100644 index 0000000..c09999b --- /dev/null +++ b/gameoflife/static/js/pattern.js @@ -0,0 +1,111 @@ +function setPattern(pattern){ + mousePattern=pattern + console.log("pattern settato: ", pattern) +} + +function showPattern(x,y,pattern){ + fill(255,255,255); + if(pattern === "insert"){ + drawRect(x,y) + } + if(pattern === "glider"){ + drawRect(x,y-1) + drawRect(x,y+1) + drawRect(x+1,y) + drawRect(x+1,y+1) + drawRect(x-1,y+1) + } + if(pattern === "oscillator"){ + drawRect(x,y) + drawRect(x,y-1) + drawRect(x,y+1) + } + if(pattern === "10row"){ + drawRect(x,y) + drawRect(x-1,y) + drawRect(x-2,y) + drawRect(x-3,y) + drawRect(x-4,y) + drawRect(x-5,y) + drawRect(x+1,y) + drawRect(x+2,y) + drawRect(x+3,y) + drawRect(x+4,y) + } + if(pattern === "tumbler"){ + drawRect(x-1,y) + drawRect(x-1,y-1) + drawRect(x-1,y-2) + drawRect(x-1,y+1) + drawRect(x-1,y+2) + drawRect(x+1,y) + drawRect(x+1,y-1) + drawRect(x+1,y-2) + drawRect(x+1,y+1) + drawRect(x+1,y+2) + drawRect(x-2,y-1) + drawRect(x-2,y-2) + drawRect(x+2,y-1) + drawRect(x+2,y-2) + drawRect(x+2,y+3) + drawRect(x-2,y+3) + drawRect(x+3,y+3) + drawRect(x+3,y+2) + drawRect(x+3,y+1) + drawRect(x-3,y+3) + drawRect(x-3,y+1) + drawRect(x-3,y+2) + } +} + +function insertPattern(x,y,pattern){ + if(pattern === "glider"){ + fill(255,255,255); + quadtree.insert(new Cell(x,y-1,true)); + quadtree.insert(new Cell(x,y+1,true)); + quadtree.insert(new Cell(x+1,y,true)); + quadtree.insert(new Cell(x+1,y+1,true)); + quadtree.insert(new Cell(x-1,y+1,true)); + } + if(pattern === "oscillator"){ + quadtree.insert(new Cell(x,y,true)); + quadtree.insert(new Cell(x,y+1,true)); + quadtree.insert(new Cell(x,y-1,true)); + } + if(pattern === "10row"){ + quadtree.insert(new Cell(x,y,true)); + quadtree.insert(new Cell(x-1,y,true)); + quadtree.insert(new Cell(x-2,y,true)); + quadtree.insert(new Cell(x-3,y,true)); + quadtree.insert(new Cell(x-4,y,true)); + quadtree.insert(new Cell(x-5,y,true)); + quadtree.insert(new Cell(x+1,y,true)); + quadtree.insert(new Cell(x+2,y,true)); + quadtree.insert(new Cell(x+3,y,true)); + quadtree.insert(new Cell(x+4,y,true)); + } + if(pattern === "tumbler"){ + quadtree.insert(new Cell(x-1,y,true)); + quadtree.insert(new Cell(x-1,y-1,true)); + quadtree.insert(new Cell(x-1,y-2,true)); + quadtree.insert(new Cell(x-1,y+1,true)); + quadtree.insert(new Cell(x-1,y+2,true)); + quadtree.insert(new Cell(x+1,y,true)); + quadtree.insert(new Cell(x+1,y-1,true)); + quadtree.insert(new Cell(x+1,y-2,true)); + quadtree.insert(new Cell(x+1,y+1,true)); + quadtree.insert(new Cell(x+1,y+2,true)); + quadtree.insert(new Cell(x-2,y-1,true)); + quadtree.insert(new Cell(x-2,y-2,true)); + quadtree.insert(new Cell(x+2,y-1,true)); + quadtree.insert(new Cell(x+2,y-2,true)); + quadtree.insert(new Cell(x+2,y+3,true)); + quadtree.insert(new Cell(x-2,y+3,true)); + quadtree.insert(new Cell(x+3,y+3,true)); + quadtree.insert(new Cell(x+3,y+2,true)); + quadtree.insert(new Cell(x+3,y+1,true)); + quadtree.insert(new Cell(x-3,y+3,true)); + quadtree.insert(new Cell(x-3,y+1,true)); + quadtree.insert(new Cell(x-3,y+2,true)); + } +} diff --git a/gameoflife/static/js/quadtree.js b/gameoflife/static/js/quadtree.js new file mode 100644 index 0000000..24f487c --- /dev/null +++ b/gameoflife/static/js/quadtree.js @@ -0,0 +1,192 @@ +const CAPACITY = 4 + +class Quadtree{ + constructor(rectangle,grid){ + // this.grid=grid; + this.rectangle = rectangle; + this.cells = []; + + //controllo se sono un quadtree finale o meno + console.log("#LOG: capacity':",this.rectangle.width*this.rectangle.height) + if(this.rectangle.width*this.rectangle.height<=CAPACITY){ + this.final=true; + } + else { + this.final=false; + } + + this.alives=0; + this.divided=false; + this.no=null; + this.ne=null; + this.so=null; + this.se=null; + } + + insert(cell){ + //console.log("#LOG: Entrato in insert con cella[",cell.x,cell.y,"]") + //se giĆ  sta non inserisco + if(grid.contains(cell)){ + //console.log("#LOG: Gia presente") + return + } + + //posso inserire solo le celle vive + if(!cell.alive) + return false; + + //console.log("#LOG: La cella e' viva") + //la cella deve appartenere al quadtree + if(cell.x(this.rectangle.start_col+this.rectangle.width-1) + || cell.y(this.rectangle.start_row+this.rectangle.height-1)) + return false; + + //posso inserire + this.alives++; + if(this.final){ + //console.log("#LOG: Sono final inserisco") + + //##CAPIRE PERCHE' IL RIFERIMENTO E' ALLA GRID INIZIALE + grid.insert(cell) + this.cells.push(cell) + + return true; + }else{ + if(!this.divided){ + //console.log("#LOG: Non sono final divido") + //divido e inserisco ricorsivamente in uno dei 4 sotto quadtree + // |0|1| + // |2|3| + this.divided=true; + + var sc=this.rectangle.start_col + var ec=this.rectangle.start_col+this.rectangle.width-1 + var sr=this.rectangle.start_row + var er=this.rectangle.start_row+this.rectangle.height-1 + var w=this.rectangle.width + var h=this.rectangle.height + + this.no=new Quadtree(new Rectangle(sc,sr,w/2,h/2),grid) + this.ne=new Quadtree(new Rectangle(sc+w/2,sr,w/2,h/2),grid) + this.so=new Quadtree(new Rectangle(sc,sr+h/2,w/2,h/2),grid) + this.se=new Quadtree(new Rectangle(sc+w/2,sr+h/2,w/2,h/2),grid) + } + + if(this.no.insert(cell)) + return true; + if(this.ne.insert(cell)) + return true; + if(this.so.insert(cell)) + return true; + if(this.se.insert(cell)) + return true; + } + } + + remove(col,row){ + + if(col(this.rectangle.start_col+this.rectangle.width-1) + || row(this.rectangle.start_row+this.rectangle.height-1)) + return + + this.alives--; + + if(this.final){ + this.cells = this.cells.filter(function(element){ + return element.x !== col || element.y !== row + }) + } + + if(this.divided){ + this.no.remove(col,row) + this.ne.remove(col,row) + this.so.remove(col,row) + this.se.remove(col,row) + } + } + + show(){ + rect(this.rectangle.start_col*RESOLUTION, + this.rectangle.start_row*RESOLUTION, + this.rectangle.width*RESOLUTION, + this.rectangle.height*RESOLUTION) + + if(this.divided){ + this.no.show() + this.ne.show() + this.so.show() + this.se.show() + } + } + + // query that returns quadtree smallest regions containing alive cells + query(result){ + if(result===undefined) + result=[] + + if(this.alives===0) + return result + + if(this.final){ + result.push(this.rectangle) + }else{ + if(this.divided){ + this.no.query(result) + this.ne.query(result) + this.so.query(result) + this.se.query(result) + } + } + + return result; + } + + queryUnifying(result){ + if(result===undefined) + result=[] + + if(this.alives===0) + return result + + if(this.final){ + result.push(this.rectangle) + }else{ + if(this.divided){ + var lno=0; + var lne=0; + var lso=0; + var lse=0; + + this.no.query(result) + this.ne.query(result) + this.so.query(result) + this.se.query(result) + } + } + + return result; + } + + + queryPoints(res){ + if(res === undefined) + res = [] + + if(this.alives === 0) + return [] + + if(this.final){ + this.cells.forEach(function(cell){ + res.push(cell) + }) + } + if(this.divided){ + this.no.queryPoints(res) + this.ne.queryPoints(res) + this.so.queryPoints(res) + this.se.queryPoints(res) + } + + return res + } +} diff --git a/gameoflife/static/js/rectangle.js b/gameoflife/static/js/rectangle.js new file mode 100644 index 0000000..500eaaf --- /dev/null +++ b/gameoflife/static/js/rectangle.js @@ -0,0 +1,8 @@ +class Rectangle{ + constructor(start_col,start_row,width,height){ + this.start_col=start_col; + this.start_row=start_row; + this.width=width; + this.height=height; + } +} diff --git a/gameoflife/static/js/sketch.js b/gameoflife/static/js/sketch.js new file mode 100644 index 0000000..8731155 --- /dev/null +++ b/gameoflife/static/js/sketch.js @@ -0,0 +1,130 @@ + +const RESOLUTION = 10 +const HEIGHT = 640 +const WIDTH = 640 + +var grid; +var quadtree; +var automatic=false +var abilityQT=true; +var abilityHover=true +var abilityPress=true +var mousePattern="none" +var gen=0; + +function setup(){ + createCanvas(HEIGHT, WIDTH); + background(0) + + rectangle = new Rectangle(0,0,width/RESOLUTION, height/RESOLUTION) + grid = new GridQT(rectangle) + quadtree = new Quadtree(grid) + + //pattern + // randomPopulate() + quadtree.insert(new Cell(2,2,true)) + quadtree.insert(new Cell(1,2,true)) + quadtree.insert(new Cell(2,1,true)) + quadtree.insert(new Cell(0,2,true)) + quadtree.insert(new Cell(1,0,true)) +} + +function draw(){ + if(abilityQT){ + if(automatic){ + gen++; + updateStats() + nextGen() + } + + }else{ + if(automatic){ + gen++; + updateStats() + normalNextGen() + } + } + + background(0) + + showActiveCells() + + // displayQuadTree() + // + // displayRelevantRegions() + + displayFrameRate() + + displayGeneration() + + mouseOver() +} + +function showActiveCells(){ + activeCell = quadtree.queryPoints() + activeCell.forEach(function(cell){ + fill(255,255,255); + // fill(random(255),random(255),random(255)); + rect(cell.x*RESOLUTION,cell.y*RESOLUTION,RESOLUTION,RESOLUTION); + }) +} + +function displayQuadTree(){ + stroke(255, 0, 0); + strokeWeight(1); + noFill() + quadtree.show() +} + +function displayRelevantRegions(){ + stroke(191, 255, 0) + strokeWeight(1); + noFill() + regions = quadtree.query() + + regions.forEach(function(e){ + rect(e.start_col*RESOLUTION, + e.start_row*RESOLUTION, + e.width*RESOLUTION, + e.height*RESOLUTION) + }); +} + +function mousePressed() { + if(abilityPress){ + x=floor(mouseX/RESOLUTION) + y=floor(mouseY/RESOLUTION) + console.log(x,y) + if(x>=0 && y>=0 && x0 && y>0 && x + + Game Of Life + + + + + + + + + + + + + + + + +
+

+

+
+
+
+ + + + + +
+
+ + From 23110271f50b957978879612571c351f9fc382bb Mon Sep 17 00:00:00 2001 From: xAlessandroC Date: Fri, 6 Oct 2023 10:11:52 +0200 Subject: [PATCH 3/3] chore - fix tests for CI/CD --- test/app_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/app_test.py b/test/app_test.py index 6af5751..3a4670c 100644 --- a/test/app_test.py +++ b/test/app_test.py @@ -8,5 +8,4 @@ def test_main_page(self): response = app.test_client().get("/") self.assertEqual(response.status_code, 200) - self.assertEqual(response.data, b"

Conway's Game of Life

")