├── .gitignore ├── README.md ├── day1 ├── README.md └── javascript │ ├── 01-variables.js │ ├── 02-controlStatements.js │ ├── 03-arrays.js │ └── 04-functions.js ├── day10 ├── README.md ├── controllers │ └── login_api.go ├── db │ ├── db.go │ ├── init.sql │ ├── player_db.go │ └── session_db.go ├── go.mod ├── go.sum ├── main.go ├── models │ ├── game.go │ ├── player.go │ └── session.go ├── router │ └── router.go └── utils │ └── utils.go ├── day11 ├── controllers │ ├── game_api.go │ └── login_api.go ├── db │ ├── db.go │ ├── game_db.go │ ├── init.sql │ ├── player_db.go │ └── session_db.go ├── go.mod ├── go.sum ├── main.go ├── models │ ├── game.go │ ├── player.go │ └── session.go ├── router │ └── router.go └── utils │ └── utils.go ├── day12 ├── README.md ├── client │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.jsx │ │ ├── components │ │ │ ├── GameBoard.css │ │ │ ├── GameBoard.jsx │ │ │ ├── Stats.css │ │ │ └── Stats.jsx │ │ ├── data │ │ │ ├── GameData.js │ │ │ └── GameLogic.js │ │ ├── index.css │ │ ├── main.jsx │ │ ├── models │ │ │ ├── Game.js │ │ │ └── Player.js │ │ ├── pages │ │ │ ├── Game.jsx │ │ │ ├── Login.jsx │ │ │ └── Signup.jsx │ │ └── utils │ │ │ ├── GameRequests.js │ │ │ └── LoginRequests.js │ └── vite.config.js └── server │ ├── controllers │ ├── game_api.go │ └── login_api.go │ ├── db │ ├── db.go │ ├── game_db.go │ ├── init.sql │ ├── player_db.go │ └── session_db.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── models │ ├── game.go │ ├── player.go │ └── session.go │ ├── router │ └── router.go │ └── utils │ └── utils.go ├── day2 ├── README.md ├── javascript │ ├── 01-classes-and-objects.js │ ├── 02-module1.mjs │ └── 03-module2.mjs └── react │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.jsx │ ├── components │ │ ├── Hello.jsx │ │ └── HelloProps.jsx │ ├── index.css │ └── main.jsx │ └── vite.config.js ├── day3 ├── README.md ├── part1 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.jsx │ │ ├── components │ │ │ └── YouShallPass.jsx │ │ ├── index.css │ │ └── main.jsx │ └── vite.config.js ├── part2 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.jsx │ │ ├── index.css │ │ └── main.jsx │ └── vite.config.js └── part3 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.jsx │ ├── index.css │ └── main.jsx │ └── vite.config.js ├── day4 ├── students │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.jsx │ │ ├── classes │ │ │ └── Student.js │ │ ├── componets │ │ │ └── Students.jsx │ │ ├── index.css │ │ └── main.jsx │ └── vite.config.js └── tictactoe_part1 │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.jsx │ ├── components │ │ ├── GameBoard.css │ │ ├── GameBoard.jsx │ │ └── Stats.jsx │ ├── data │ │ ├── GameData.js │ │ └── GameLogic.js │ ├── index.css │ └── main.jsx │ └── vite.config.js ├── day5 ├── .gitignore ├── README.md ├── index.html ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.css │ ├── App.jsx │ ├── components │ │ ├── GameBoard.css │ │ ├── GameBoard.jsx │ │ ├── Stats.css │ │ └── Stats.jsx │ ├── data │ │ ├── GameData.js │ │ └── GameLogic.js │ ├── index.css │ └── main.jsx └── vite.config.js ├── day6 ├── 01-variables.go ├── 02-controlStatements.go ├── 03-arrays.go ├── 04-maps.go ├── 05-functions.go ├── 06-objects.go ├── 07-errorHandling │ ├── Main.java │ ├── go.mod │ └── main.go └── README.md ├── day7 ├── README.md ├── queries.sql └── rest-apis │ ├── APIs Invocations.postman_collection.json │ ├── Pizza API.postman_collection.json │ ├── bmi-api │ └── main.go │ └── pizza-api │ └── main.go ├── day8 ├── README.md ├── db │ ├── db.go │ ├── init.sql │ └── player_db.go ├── go.mod ├── go.sum ├── main.go └── models │ └── player.go └── day9 ├── README.md ├── controllers └── login_api.go ├── db ├── db.go ├── init.sql └── player_db.go ├── go.mod ├── go.sum ├── main.go ├── models ├── player.go └── session.go └── router └── router.go /.gitignore: -------------------------------------------------------------------------------- 1 | ./**/node_modules/ 2 | ./**/go.sum 3 | ./**/**/go.sum 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learn Web Development by Building a Multiplayer Game 2 | 3 | ### Day 1: 4 | 5 | * Agenda 6 | * Introduction to Frontend web development 7 | * Introduction to React 8 | * NodeJS, NPM, and VS Code Installation 9 | 10 | ### Day 2: 11 | 12 | * Final Game Preview 13 | * Array recap 14 | * JS classes 15 | * JS objects 16 | * Create Vite Project 17 | * Introduction to React 18 | * React components (definition, props, styles) 19 | 20 | ### Day 3: 21 | 22 | * Conditional components display in React 23 | * Simple todo list 24 | * Value binding 25 | * React states 26 | * Display array's elements as react components 27 | 28 | ### Day 4: 29 | 30 | * Game's logic 31 | * Data encapsulation 32 | * Logic encapsulation 33 | * Game’s initial view 34 | * Objects as props 35 | * Update object’s value/s from inside of the component 36 | 37 | ### Day 5: 38 | 39 | * Styling... 40 | 41 | ### Day 6: 42 | 43 | * Go, VS Code, and Postman Installation 44 | * Introduction to Go 45 | * variables, if, switch, for, functions, literal functions 46 | * arrays, maps, structs, interfaces, packages 47 | 48 | ### Day 7: 49 | 50 | * How the final game will look like 51 | * REST APIs 52 | * Structure of a REST API (request and response) 53 | * HTTP methods 54 | * Type of bodies 55 | * JSON 56 | * Examples 57 | * Relational Databases and MySQL(MariaDB) introduction 58 | * MariaDB installation 59 | * CRUD oprtations 60 | * create a table 61 | * create a new record 62 | * retrive a record 63 | * update a record 64 | * delete a record 65 | 66 | ### Day 8: 67 | 68 | * Database operations in Go 69 | * Writing a helper struct to wrap database operations for the player model 70 | * Viewing and handling database errors 71 | 72 | ### Day 9: 73 | 74 | * Utilizing databases in REST APIs 75 | * Creating player login REST API 76 | * Hashing a password 77 | 78 | ### Day 10: 79 | 80 | * Finalizing player login REST API 81 | * Creating game data model 82 | 83 | ### Day 11: 84 | 85 | * Creating game's database wrapper 86 | * Creating game's RESS API controller 87 | 88 | ### Day 12: 89 | 90 | * React Router 91 | * Using fetch to make REST API requests to the backend 92 | * Finalizing 93 | 94 | 95 | -------------------------------------------------------------------------------- /day1/README.md: -------------------------------------------------------------------------------- 1 | # Day 1's Links 2 | 3 | - NodeJS 4 | `https://nodejs.org/en/download/` 5 | - Visual Studio Code (VS Code) 6 | `https://code.visualstudio.com/` 7 | - React VS Code Extension 8 | `https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets` 9 | - Vite 10 | `https://vitejs.dev/guide/` 11 | -------------------------------------------------------------------------------- /day1/javascript/01-variables.js: -------------------------------------------------------------------------------- 1 | let x = 12; 2 | let y = x + 8; 3 | console.log("y = ", y); 4 | 5 | let z = 12.4; 6 | console.log("z + x = ", z + x); 7 | 8 | let name = "Ali"; 9 | console.log("Hello,", name); 10 | 11 | let firstName = "Ali"; 12 | let lastName = "Al-Homsi"; 13 | let fullName = `${firstName} ${lastName}`; 14 | console.log("Hello", fullName); 15 | 16 | let canIDrive = false; 17 | console.log("Am I old enough to drive?"); 18 | -------------------------------------------------------------------------------- /day1/javascript/02-controlStatements.js: -------------------------------------------------------------------------------- 1 | let shallIPass = true; 2 | if (shallIPass) { 3 | console.log("You shall pass"); 4 | } else { 5 | console.log("You shall not pass"); 6 | } 7 | 8 | let x = 12, 9 | y = 10, 10 | z = 13; 11 | 12 | if (x > y && x > z) { 13 | console.log("x is the greatest"); 14 | } else if (y > x && y > z) { 15 | console.log("y is the greatest"); 16 | } else if (z > x && z > y) { 17 | console.log("z is the greatest"); 18 | } 19 | 20 | let mark = "B"; 21 | switch (mark) { 22 | case "A": 23 | console.log("big nerd"); 24 | break; 25 | case "B": 26 | console.log("nerd"); 27 | break; 28 | case "C": 29 | console.log("ain't much"); 30 | break; 31 | default: 32 | console.log("I don't know what is that!"); 33 | } 34 | 35 | for (let i = 1; i <= 10; i++) { 36 | console.log(i); 37 | } 38 | 39 | let count = 10; 40 | while (count > 0) { 41 | console.log(count); 42 | count--; 43 | } 44 | -------------------------------------------------------------------------------- /day1/javascript/03-arrays.js: -------------------------------------------------------------------------------- 1 | let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 2 | console.log("all array's elements", arr); 3 | 4 | console.log("element by element"); 5 | for (let i = 0; i < arr.length; i++) { 6 | console.log(arr[i]); 7 | } 8 | 9 | arr[0] = 99; 10 | console.log(arr); 11 | 12 | console.log("using array's forEach method"); 13 | arr.forEach((element) => console.log(element)); 14 | 15 | let doubleVals = arr.map((element) => { 16 | return element * 2; 17 | }); 18 | console.log(doubleVals); 19 | 20 | console.log( 21 | arr.findIndex((element) => { 22 | return element === 99; 23 | }) 24 | ); 25 | -------------------------------------------------------------------------------- /day1/javascript/04-functions.js: -------------------------------------------------------------------------------- 1 | function nothing() { 2 | console.log("nothing"); 3 | } 4 | 5 | nothing(); 6 | 7 | function greet(name) { 8 | console.log(`Hello, ${name}`); 9 | } 10 | 11 | greet("Jaber"); 12 | 13 | function square(n) { 14 | return n * n; 15 | } 16 | 17 | console.log(square(4)); 18 | 19 | const nothing2 = () => { 20 | console.log("nothing2"); 21 | }; 22 | 23 | nothing2(); 24 | 25 | const add = (x, y, z) => { 26 | return x + y + z; 27 | }; 28 | 29 | console.log(add(1, 2, 3)); 30 | -------------------------------------------------------------------------------- /day10/README.md: -------------------------------------------------------------------------------- 1 | # Day 10: 2 | 3 | - Install `uuid` for Go: 4 | - Open terminal or CMD inside a project 5 | - Run `go get -u github.com/google/uuid` 6 | - Check `controllers/login_api.go` for more details about the hash 7 | -------------------------------------------------------------------------------- /day10/controllers/login_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "tictactoe/db" 9 | "tictactoe/models" 10 | 11 | "github.com/google/uuid" 12 | "golang.org/x/crypto/bcrypt" 13 | ) 14 | 15 | type LoginAPI struct { 16 | playerDB *db.PlayerDB 17 | sessionDB *db.SessionDB 18 | } 19 | 20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI { 21 | return &LoginAPI{ 22 | playerDB: playerDB, 23 | sessionDB: sessionDB, 24 | } 25 | } 26 | 27 | type reqBody struct { 28 | Username string `json:"username"` 29 | Password string `json:"password"` 30 | } 31 | 32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) { 33 | var requestBody reqBody 34 | 35 | err := json.NewDecoder(req.Body).Decode(&requestBody) 36 | if err != nil { 37 | resp.WriteHeader(http.StatusBadRequest) 38 | return 39 | } 40 | 41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost) 42 | if err != nil { 43 | resp.WriteHeader(http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | requestBody.Password = string(hashed) 48 | 49 | player, err := l.playerDB.Create(models.Player{ 50 | Username: requestBody.Username, 51 | Password: requestBody.Password, 52 | }) 53 | if err != nil { 54 | resp.WriteHeader(http.StatusInternalServerError) 55 | return 56 | } 57 | 58 | resp.Header().Set("Content-Type", "application/json") 59 | json.NewEncoder(resp).Encode(player) 60 | } 61 | 62 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) { 63 | var requestBody reqBody 64 | 65 | err := json.NewDecoder(req.Body).Decode(&requestBody) 66 | if err != nil { 67 | resp.WriteHeader(http.StatusBadRequest) 68 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎") 69 | return 70 | } 71 | 72 | player, err := l.playerDB.Get(requestBody.Username) 73 | if err != nil { 74 | resp.WriteHeader(http.StatusUnauthorized) 75 | fmt.Fprintln(resp, "username is incorrect 👎") 76 | return 77 | } 78 | 79 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password)) 80 | if err != nil { 81 | resp.WriteHeader(http.StatusUnauthorized) 82 | fmt.Fprintln(resp, "password is incorrect 👎") 83 | return 84 | } 85 | 86 | session := models.Session{ 87 | Username: player.Username, 88 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""), 89 | } 90 | 91 | err = l.sessionDB.Create(session) 92 | if err != nil { 93 | resp.WriteHeader(http.StatusInternalServerError) 94 | fmt.Fprintln(resp, "something went wrong ☹️") 95 | return 96 | } 97 | 98 | resp.Header().Set("Authorization", session.Token) 99 | fmt.Fprintln(resp, "ok 👍") 100 | } 101 | 102 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) { 103 | token := req.Header.Get("Authorization") 104 | 105 | session, err := l.sessionDB.Get(token) 106 | if err != nil { 107 | resp.WriteHeader(http.StatusUnauthorized) 108 | fmt.Fprintln(resp, "invalid token 👎") 109 | return 110 | } 111 | 112 | player, err := l.playerDB.Get(session.Username) 113 | if err != nil { 114 | resp.WriteHeader(http.StatusInternalServerError) 115 | fmt.Fprintln(resp, "something went wrong") 116 | return 117 | } 118 | 119 | player.Password = "" 120 | 121 | resp.Header().Set("Content-Type", "application/json") 122 | json.NewEncoder(resp).Encode(player) 123 | } 124 | -------------------------------------------------------------------------------- /day10/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var connectionInstance *sql.DB = nil 10 | 11 | func GetInstance() *sql.DB { 12 | if connectionInstance == nil { 13 | var err error 14 | // DB_USERNAME is root by default 15 | // DB_PASSWORD is what you've set when installing the database 16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName` 17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True") 18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True") 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | return connectionInstance 24 | } 25 | -------------------------------------------------------------------------------- /day10/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ttt; 2 | 3 | CREATE TABLE IF NOT EXISTS ttt.players ( 4 | id INTEGER AUTO_INCREMENT, 5 | username VARCHAR(60) UNIQUE NOT NULL, 6 | password VARCHAR(200), 7 | created_at TIMESTAMP, 8 | updated_at TIMESTAMP, 9 | PRIMARY KEY (id) 10 | ); 11 | 12 | CREATE TABLE IF NOT EXISTS ttt.sessions ( 13 | id INTEGER AUTO_INCREMENT, 14 | username VARCHAR(60), 15 | token VARCHAR(32) UNIQUE, 16 | PRIMARY KEY (id) 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS ttt.game ( 20 | id INTEGER AUTO_INCREMENT, 21 | x_player_id INTEGER, 22 | o_player_id INTEGER, 23 | x_score INTEGER, 24 | o_score INTEGER, 25 | cells VARCHAR(20), 26 | current_player_id INTEGER, 27 | PRIMARY KEY (id), 28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id), 29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id) 30 | ); 31 | -------------------------------------------------------------------------------- /day10/db/player_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type PlayerDB struct{} 6 | 7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)", 10 | player.Username, player.Password) 11 | if err != nil { 12 | return models.Player{}, err 13 | } 14 | 15 | playerId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Player{}, err 18 | } 19 | 20 | player.Id = int(playerId) 21 | 22 | return player, nil 23 | } 24 | 25 | func (p *PlayerDB) Get(username string) (models.Player, error) { 26 | var player models.Player 27 | err := GetInstance(). 28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username). 29 | Scan(&player.Id, &player.Username, &player.Password) 30 | 31 | if err != nil { 32 | return models.Player{}, err 33 | } 34 | 35 | return player, nil 36 | } 37 | 38 | func (p *PlayerDB) Update(username string, player models.Player) error { 39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (p *PlayerDB) Delete(username string) error { 47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } -------------------------------------------------------------------------------- /day10/db/session_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type SessionDB struct{} 6 | 7 | func (s *SessionDB) Create(session models.Session) error { 8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token) 9 | if err != nil { 10 | return err 11 | } 12 | return nil 13 | } 14 | 15 | func (s *SessionDB) Get(token string) (models.Session, error) { 16 | var session models.Session 17 | 18 | err := GetInstance(). 19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token). 20 | Scan(&session.Username, &session.Token) 21 | if err != nil { 22 | return models.Session{}, err 23 | } 24 | 25 | return session, nil 26 | } 27 | -------------------------------------------------------------------------------- /day10/go.mod: -------------------------------------------------------------------------------- 1 | module tictactoe 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.0 // indirect 7 | github.com/google/uuid v1.3.0 // indirect 8 | golang.org/x/crypto v0.6.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /day10/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 7 | -------------------------------------------------------------------------------- /day10/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "tictactoe/router" 4 | 5 | func main() { 6 | router.StartServer() 7 | } 8 | -------------------------------------------------------------------------------- /day10/models/game.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "tictactoe/utils" 8 | ) 9 | 10 | type Game struct { 11 | Id int `json:"id"` 12 | XPlayerId int `json:"xPlayerId"` 13 | OPlayerId int `json:"oPlayerId"` 14 | XScore int `json:"xScore"` 15 | OScore int `json:"oScore"` 16 | Cells string `json:"cells"` 17 | CurrentPlayerId int `json:"currentPlayerId"` 18 | } 19 | 20 | func (g *Game) resetGame() { 21 | g.Cells = "1 2 3 4 5 6 7 8 9" 22 | g.CurrentPlayerId = g.XPlayerId 23 | } 24 | 25 | func (g *Game) switchTurns() { 26 | if g.CurrentPlayerId == g.XPlayerId { 27 | g.CurrentPlayerId = g.OPlayerId 28 | } else if g.CurrentPlayerId == g.OPlayerId { 29 | g.CurrentPlayerId = g.XPlayerId 30 | } 31 | } 32 | 33 | func (g *Game) MarkCell(cellNumber int) error { 34 | if !g.isCellNumberInBounds(cellNumber) { 35 | return errors.New("cell number is not in bound") 36 | } 37 | 38 | cells := strings.Split(g.Cells, " ") 39 | if g.areAllCellsOccupied(cells) { 40 | return errors.New("all game's cells are occupied") 41 | } 42 | 43 | if g.isCellOccupied(cells, cellNumber) { 44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber]) 45 | } 46 | 47 | currentPlayer := g.getCurrentPlayerSymbol() 48 | cells[cellNumber] = currentPlayer 49 | g.switchTurns() 50 | 51 | return nil 52 | } 53 | 54 | func (g *Game) CheckWinnerAndUpdateScore() { 55 | cells := strings.Split(g.Cells, " ") 56 | 57 | if g.areAllCellsOccupied(cells) { 58 | g.resetGame() 59 | return 60 | } 61 | 62 | winner := utils.CheckWinner(cells) 63 | switch winner { 64 | case "X": 65 | g.XScore++ 66 | case "O": 67 | g.OScore++ 68 | } 69 | 70 | if len(winner) > 0 { 71 | g.resetGame() 72 | } 73 | } 74 | 75 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool { 76 | return cells[cellNumber] != "X" && cells[cellNumber] != "O" 77 | } 78 | 79 | func (g *Game) areAllCellsOccupied(cells []string) bool { 80 | for index, _ := range cells { 81 | if !g.isCellOccupied(cells, index) { 82 | return false 83 | } 84 | } 85 | return true 86 | } 87 | 88 | func (g *Game) isCellNumberInBounds(cellNumber int) bool { 89 | return cellNumber < 9 && cellNumber >= 0 90 | } 91 | 92 | func (g *Game) getCurrentPlayerSymbol() string { 93 | if g.CurrentPlayerId == g.XPlayerId { 94 | return "X" 95 | } else if g.CurrentPlayerId == g.OPlayerId { 96 | return "O" 97 | } 98 | return "" 99 | } 100 | -------------------------------------------------------------------------------- /day10/models/player.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Player struct { 6 | Id int `json:"id"` 7 | Username string `json:"username"` 8 | Password string `json:"password"` 9 | CreatedAt time.Time `json:"-"` 10 | UpdatedAt time.Time `json:"-"` 11 | } 12 | -------------------------------------------------------------------------------- /day10/models/session.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Session struct { 4 | Username string `json:"-"` 5 | Token string `json:"-"` 6 | } 7 | -------------------------------------------------------------------------------- /day10/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "tictactoe/controllers" 7 | "tictactoe/db" 8 | ) 9 | 10 | func StartServer() { 11 | playerDB := new(db.PlayerDB) 12 | sessionDB := new(db.SessionDB) 13 | 14 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB) 15 | 16 | router := http.NewServeMux() 17 | router.HandleFunc("/player/login", loginAPI.HandleLogin) 18 | router.HandleFunc("/player/signup", loginAPI.HandleSignup) 19 | router.HandleFunc("/player/token", loginAPI.HandleTokenLogin) 20 | 21 | log.Println("started server at http://localhost:8080") 22 | http.ListenAndServe(":8080", router) 23 | } 24 | -------------------------------------------------------------------------------- /day10/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func CheckWinner(cells []string) string { 4 | if checkIfPlayerWon(cells, "X") { 5 | return "X" 6 | } else if checkIfPlayerWon(cells, "O") { 7 | return "O" 8 | } 9 | return "" 10 | } 11 | 12 | func checkIfPlayerWon(cells []string, playerType string) bool { 13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType 14 | 15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType 16 | 17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType 18 | 19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType 20 | 21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType 22 | 23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType 24 | 25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType 26 | 27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType 28 | 29 | return firstColumn || 30 | secondColumn || 31 | thirdColumn || 32 | firstRow || 33 | secondRow || 34 | thirdRow || 35 | firstDiagonal || 36 | secondDiagonal 37 | } 38 | -------------------------------------------------------------------------------- /day11/controllers/game_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "tictactoe/db" 9 | "tictactoe/models" 10 | ) 11 | 12 | type GameAPI struct { 13 | gameDB *db.GameDB 14 | playerDB *db.PlayerDB 15 | sessionDB *db.SessionDB 16 | } 17 | 18 | func NewGameAPI(gameDB *db.GameDB, playerDB *db.PlayerDB, sessionDB *db.SessionDB) *GameAPI { 19 | return &GameAPI{ 20 | gameDB: gameDB, 21 | playerDB: playerDB, 22 | sessionDB: sessionDB, 23 | } 24 | } 25 | 26 | func (g *GameAPI) setupResponseHeaders(resp http.ResponseWriter) { 27 | resp.Header().Set("Content-Type", "application/json") 28 | } 29 | 30 | func (g *GameAPI) HandleNewGame(resp http.ResponseWriter, req *http.Request) { 31 | token := req.Header.Get("Authorization") 32 | if len(token) == 0 { 33 | resp.WriteHeader(http.StatusUnauthorized) 34 | return 35 | } 36 | 37 | player, err := g.fetchPlayerUsingToken(token) 38 | if err != nil { 39 | resp.WriteHeader(http.StatusUnauthorized) 40 | return 41 | } 42 | 43 | game := models.Game{ 44 | XPlayerId: player.Id, 45 | } 46 | 47 | newGame, err := g.gameDB.Create(game) 48 | if err != nil { 49 | resp.WriteHeader(http.StatusInternalServerError) 50 | return 51 | } 52 | 53 | g.setupResponseHeaders(resp) 54 | json.NewEncoder(resp).Encode(newGame) 55 | } 56 | 57 | func (g *GameAPI) HandleJoinGame(resp http.ResponseWriter, req *http.Request) { 58 | gameId := req.URL.Query().Get("id") 59 | if len(gameId) == 0 { 60 | resp.WriteHeader(http.StatusBadRequest) 61 | return 62 | } 63 | 64 | intGameId, err := strconv.Atoi(gameId) 65 | if len(gameId) == 0 { 66 | resp.WriteHeader(http.StatusBadRequest) 67 | return 68 | } 69 | 70 | token := req.Header.Get("Authorization") 71 | if len(token) == 0 { 72 | resp.WriteHeader(http.StatusUnauthorized) 73 | return 74 | } 75 | 76 | player, err := g.fetchPlayerUsingToken(token) 77 | if err != nil { 78 | resp.WriteHeader(http.StatusUnauthorized) 79 | return 80 | } 81 | 82 | game, err := g.gameDB.Get(intGameId) 83 | if err != nil { 84 | fmt.Println(err) 85 | resp.WriteHeader(http.StatusNotFound) 86 | return 87 | } 88 | 89 | if game.OPlayerId != 0 { 90 | resp.WriteHeader(http.StatusBadRequest) 91 | fmt.Fprintln(resp, "THIS GAME IS FULLY OCCUPIED!!!") 92 | return 93 | } 94 | 95 | game.OPlayerId = player.Id 96 | 97 | err = g.gameDB.Update(game.Id, game) 98 | if err != nil { 99 | resp.WriteHeader(http.StatusInternalServerError) 100 | return 101 | } 102 | 103 | g.setupResponseHeaders(resp) 104 | json.NewEncoder(resp).Encode(game) 105 | } 106 | 107 | func (g *GameAPI) HandlePlayTurn(resp http.ResponseWriter, req *http.Request) { 108 | var reqBody struct { 109 | GameId int `json:"gameId"` 110 | CellNumber int `json:"cellNumber"` 111 | } 112 | 113 | err := json.NewDecoder(req.Body).Decode(&reqBody) 114 | if err != nil { 115 | resp.WriteHeader(http.StatusBadRequest) 116 | return 117 | } 118 | 119 | token := req.Header.Get("Authorization") 120 | if len(token) == 0 { 121 | resp.WriteHeader(http.StatusUnauthorized) 122 | return 123 | } 124 | 125 | player, err := g.fetchPlayerUsingToken(token) 126 | if err != nil { 127 | resp.WriteHeader(http.StatusUnauthorized) 128 | return 129 | } 130 | 131 | game, err := g.gameDB.Get(reqBody.GameId) 132 | if err != nil { 133 | resp.WriteHeader(http.StatusInternalServerError) 134 | return 135 | } 136 | 137 | if player.Id != game.CurrentPlayerId { 138 | resp.WriteHeader(http.StatusBadRequest) 139 | fmt.Fprintln(resp, "this player can't play right now") 140 | return 141 | } 142 | 143 | err = game.MarkCell(reqBody.CellNumber) 144 | if err != nil { 145 | resp.WriteHeader(http.StatusBadRequest) 146 | fmt.Fprintln(resp, err.Error()) 147 | return 148 | } 149 | 150 | game.CheckWinnerAndUpdateScore() 151 | 152 | err = g.gameDB.Update(game.Id, game) 153 | if err != nil { 154 | resp.WriteHeader(http.StatusInternalServerError) 155 | return 156 | } 157 | 158 | g.setupResponseHeaders(resp) 159 | json.NewEncoder(resp).Encode(game) 160 | } 161 | 162 | func (g *GameAPI) HandleGetGame(resp http.ResponseWriter, req *http.Request) { 163 | gameId := req.URL.Query().Get("id") 164 | if len(gameId) == 0 { 165 | resp.WriteHeader(http.StatusBadRequest) 166 | return 167 | } 168 | 169 | intGameId, err := strconv.Atoi(gameId) 170 | if len(gameId) == 0 { 171 | resp.WriteHeader(http.StatusBadRequest) 172 | return 173 | } 174 | 175 | token := req.Header.Get("Authorization") 176 | if len(token) == 0 { 177 | resp.WriteHeader(http.StatusUnauthorized) 178 | return 179 | } 180 | 181 | player, err := g.fetchPlayerUsingToken(token) 182 | if err != nil { 183 | resp.WriteHeader(http.StatusUnauthorized) 184 | return 185 | } 186 | 187 | game, err := g.gameDB.Get(intGameId) 188 | if err != nil { 189 | resp.WriteHeader(http.StatusInternalServerError) 190 | return 191 | } 192 | 193 | if player.Id != game.XPlayerId && player.Id != game.OPlayerId { 194 | resp.WriteHeader(http.StatusUnauthorized) 195 | fmt.Fprintln(resp, "this player does not belong to this game!") 196 | return 197 | } 198 | 199 | g.setupResponseHeaders(resp) 200 | json.NewEncoder(resp).Encode(game) 201 | } 202 | 203 | func (g *GameAPI) fetchPlayerUsingToken(token string) (models.Player, error) { 204 | session, err := g.sessionDB.Get(token) 205 | if err != nil { 206 | return models.Player{}, err 207 | } 208 | 209 | player, err := g.playerDB.Get(session.Username) 210 | if err != nil { 211 | return models.Player{}, err 212 | } 213 | 214 | return player, nil 215 | } 216 | -------------------------------------------------------------------------------- /day11/controllers/login_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "tictactoe/db" 9 | "tictactoe/models" 10 | 11 | "github.com/google/uuid" 12 | "golang.org/x/crypto/bcrypt" 13 | ) 14 | 15 | type LoginAPI struct { 16 | playerDB *db.PlayerDB 17 | sessionDB *db.SessionDB 18 | } 19 | 20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI { 21 | return &LoginAPI{ 22 | playerDB: playerDB, 23 | sessionDB: sessionDB, 24 | } 25 | } 26 | 27 | type reqBody struct { 28 | Username string `json:"username"` 29 | Password string `json:"password"` 30 | } 31 | 32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) { 33 | var requestBody reqBody 34 | 35 | err := json.NewDecoder(req.Body).Decode(&requestBody) 36 | if err != nil { 37 | resp.WriteHeader(http.StatusBadRequest) 38 | return 39 | } 40 | 41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost) 42 | if err != nil { 43 | resp.WriteHeader(http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | requestBody.Password = string(hashed) 48 | 49 | player, err := l.playerDB.Create(models.Player{ 50 | Username: requestBody.Username, 51 | Password: requestBody.Password, 52 | }) 53 | if err != nil { 54 | resp.WriteHeader(http.StatusInternalServerError) 55 | return 56 | } 57 | 58 | resp.Header().Set("Content-Type", "application/json") 59 | json.NewEncoder(resp).Encode(player) 60 | } 61 | 62 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) { 63 | var requestBody reqBody 64 | 65 | err := json.NewDecoder(req.Body).Decode(&requestBody) 66 | if err != nil { 67 | resp.WriteHeader(http.StatusBadRequest) 68 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎") 69 | return 70 | } 71 | 72 | player, err := l.playerDB.Get(requestBody.Username) 73 | if err != nil { 74 | resp.WriteHeader(http.StatusUnauthorized) 75 | fmt.Fprintln(resp, "username is incorrect 👎") 76 | return 77 | } 78 | 79 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password)) 80 | if err != nil { 81 | resp.WriteHeader(http.StatusUnauthorized) 82 | fmt.Fprintln(resp, "password is incorrect 👎") 83 | return 84 | } 85 | 86 | session := models.Session{ 87 | Username: player.Username, 88 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""), 89 | } 90 | 91 | err = l.sessionDB.Create(session) 92 | if err != nil { 93 | resp.WriteHeader(http.StatusInternalServerError) 94 | fmt.Fprintln(resp, "something went wrong ☹️") 95 | return 96 | } 97 | 98 | resp.Header().Set("Authorization", session.Token) 99 | fmt.Fprintln(resp, "ok 👍") 100 | } 101 | 102 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) { 103 | token := req.Header.Get("Authorization") 104 | 105 | session, err := l.sessionDB.Get(token) 106 | if err != nil { 107 | resp.WriteHeader(http.StatusUnauthorized) 108 | fmt.Fprintln(resp, "invalid token 👎") 109 | return 110 | } 111 | 112 | player, err := l.playerDB.Get(session.Username) 113 | if err != nil { 114 | resp.WriteHeader(http.StatusInternalServerError) 115 | fmt.Fprintln(resp, "something went wrong") 116 | return 117 | } 118 | 119 | player.Password = "" 120 | 121 | resp.Header().Set("Content-Type", "application/json") 122 | json.NewEncoder(resp).Encode(player) 123 | } 124 | -------------------------------------------------------------------------------- /day11/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var connectionInstance *sql.DB = nil 10 | 11 | func GetInstance() *sql.DB { 12 | if connectionInstance == nil { 13 | var err error 14 | // DB_USERNAME is root by default 15 | // DB_PASSWORD is what you've set when installing the database 16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName` 17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True") 18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True") 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | return connectionInstance 24 | } 25 | -------------------------------------------------------------------------------- /day11/db/game_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type GameDB struct{} 6 | 7 | func (g *GameDB) Create(game models.Game) (models.Game, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.games (x_player_id, o_player_id, x_score, o_score, current_player_id, cells) VALUES (?, ?, ?, ?, ?, ?)", 10 | game.XPlayerId, 0, 0, 0, game.XPlayerId, "1 2 3 4 5 6 7 8 9") 11 | if err != nil { 12 | return models.Game{}, err 13 | } 14 | 15 | lastId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Game{}, err 18 | } 19 | 20 | game.Id = int(lastId) 21 | game.Cells = "1 2 3 4 5 6 7 8 9" 22 | game.CurrentPlayerId = game.XPlayerId 23 | game.XScore = 0 24 | game.OScore = 0 25 | 26 | return game, nil 27 | } 28 | 29 | func (g *GameDB) Get(id int) (models.Game, error) { 30 | var game models.Game 31 | err := GetInstance(). 32 | QueryRow("SELECT * FROM ttt.games WHERE id=?", id). 33 | Scan(&game.Id, &game.XPlayerId, &game.OPlayerId, &game.XScore, &game.OScore, 34 | &game.Cells, &game.CurrentPlayerId) 35 | if err != nil { 36 | return models.Game{}, err 37 | } 38 | 39 | return game, nil 40 | } 41 | 42 | func (p *GameDB) Update(id int, game models.Game) error { 43 | _, err := GetInstance().Exec( 44 | "UPDATE ttt.games SET cells=?, x_score=?, o_score=?, current_player_id=?, o_player_id=?", 45 | game.Cells, game.XScore, game.OScore, game.CurrentPlayerId, game.OPlayerId) 46 | if err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func (p *GameDB) Delete(id int) error { 53 | _, err := GetInstance().Exec("DELETE FROM ttt.games WHERE id=?", id) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /day11/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ttt; 2 | 3 | CREATE TABLE IF NOT EXISTS ttt.players ( 4 | id INTEGER AUTO_INCREMENT, 5 | username VARCHAR(60) UNIQUE NOT NULL, 6 | password VARCHAR(200), 7 | created_at TIMESTAMP, 8 | updated_at TIMESTAMP, 9 | PRIMARY KEY (id) 10 | ); 11 | 12 | CREATE TABLE IF NOT EXISTS ttt.sessions ( 13 | id INTEGER AUTO_INCREMENT, 14 | username VARCHAR(60), 15 | token VARCHAR(32) UNIQUE, 16 | PRIMARY KEY (id) 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS ttt.game ( 20 | id INTEGER AUTO_INCREMENT, 21 | x_player_id INTEGER, 22 | o_player_id INTEGER, 23 | x_score INTEGER, 24 | o_score INTEGER, 25 | cells VARCHAR(20), 26 | current_player_id INTEGER, 27 | PRIMARY KEY (id), 28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id), 29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id) 30 | ); 31 | -------------------------------------------------------------------------------- /day11/db/player_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type PlayerDB struct{} 6 | 7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)", 10 | player.Username, player.Password) 11 | if err != nil { 12 | return models.Player{}, err 13 | } 14 | 15 | playerId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Player{}, err 18 | } 19 | 20 | player.Id = int(playerId) 21 | 22 | return player, nil 23 | } 24 | 25 | func (p *PlayerDB) Get(username string) (models.Player, error) { 26 | var player models.Player 27 | err := GetInstance(). 28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username). 29 | Scan(&player.Id, &player.Username, &player.Password) 30 | 31 | if err != nil { 32 | return models.Player{}, err 33 | } 34 | 35 | return player, nil 36 | } 37 | 38 | func (p *PlayerDB) Update(username string, player models.Player) error { 39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (p *PlayerDB) Delete(username string) error { 47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } -------------------------------------------------------------------------------- /day11/db/session_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type SessionDB struct{} 6 | 7 | func (s *SessionDB) Create(session models.Session) error { 8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token) 9 | if err != nil { 10 | return err 11 | } 12 | return nil 13 | } 14 | 15 | func (s *SessionDB) Get(token string) (models.Session, error) { 16 | var session models.Session 17 | 18 | err := GetInstance(). 19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token). 20 | Scan(&session.Username, &session.Token) 21 | if err != nil { 22 | return models.Session{}, err 23 | } 24 | 25 | return session, nil 26 | } 27 | -------------------------------------------------------------------------------- /day11/go.mod: -------------------------------------------------------------------------------- 1 | module tictactoe 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.0 // indirect 7 | github.com/google/uuid v1.3.0 // indirect 8 | golang.org/x/crypto v0.6.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /day11/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 7 | -------------------------------------------------------------------------------- /day11/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "tictactoe/router" 4 | 5 | func main() { 6 | router.StartServer() 7 | } 8 | -------------------------------------------------------------------------------- /day11/models/game.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "tictactoe/utils" 8 | ) 9 | 10 | type Game struct { 11 | Id int `json:"id"` 12 | XPlayerId int `json:"xPlayerId"` 13 | OPlayerId int `json:"oPlayerId"` 14 | XScore int `json:"xScore"` 15 | OScore int `json:"oScore"` 16 | Cells string `json:"cells"` 17 | CurrentPlayerId int `json:"currentPlayerId"` 18 | } 19 | 20 | func (g *Game) resetGame() { 21 | g.Cells = "1 2 3 4 5 6 7 8 9" 22 | g.CurrentPlayerId = g.XPlayerId 23 | } 24 | 25 | func (g *Game) switchTurns() { 26 | if g.CurrentPlayerId == g.XPlayerId { 27 | g.CurrentPlayerId = g.OPlayerId 28 | } else if g.CurrentPlayerId == g.OPlayerId { 29 | g.CurrentPlayerId = g.XPlayerId 30 | } 31 | } 32 | 33 | func (g *Game) MarkCell(cellNumber int) error { 34 | if !g.isCellNumberInBounds(cellNumber) { 35 | return errors.New("cell number is not in bound") 36 | } 37 | 38 | cells := strings.Split(g.Cells, " ") 39 | if g.areAllCellsOccupied(cells) { 40 | return errors.New("all game's cells are occupied") 41 | } 42 | 43 | if g.isCellOccupied(cells, cellNumber) { 44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber]) 45 | } 46 | 47 | currentPlayer := g.getCurrentPlayerSymbol() 48 | cells[cellNumber] = currentPlayer 49 | g.Cells = strings.Join(cells, " ") 50 | 51 | g.switchTurns() 52 | 53 | return nil 54 | } 55 | 56 | func (g *Game) CheckWinnerAndUpdateScore() { 57 | cells := strings.Split(g.Cells, " ") 58 | 59 | if g.areAllCellsOccupied(cells) { 60 | g.resetGame() 61 | return 62 | } 63 | 64 | winner := utils.CheckWinner(cells) 65 | switch winner { 66 | case "X": 67 | g.XScore++ 68 | case "O": 69 | g.OScore++ 70 | } 71 | 72 | if len(winner) > 0 { 73 | g.resetGame() 74 | } 75 | } 76 | 77 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool { 78 | return cells[cellNumber] == "X" || cells[cellNumber] == "O" 79 | } 80 | 81 | func (g *Game) areAllCellsOccupied(cells []string) bool { 82 | for index, _ := range cells { 83 | if !g.isCellOccupied(cells, index) { 84 | return false 85 | } 86 | } 87 | return true 88 | } 89 | 90 | func (g *Game) isCellNumberInBounds(cellNumber int) bool { 91 | return cellNumber < 9 && cellNumber >= 0 92 | } 93 | 94 | func (g *Game) getCurrentPlayerSymbol() string { 95 | if g.CurrentPlayerId == g.XPlayerId { 96 | return "X" 97 | } else if g.CurrentPlayerId == g.OPlayerId { 98 | return "O" 99 | } 100 | return "" 101 | } 102 | -------------------------------------------------------------------------------- /day11/models/player.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Player struct { 6 | Id int `json:"id"` 7 | Username string `json:"username"` 8 | Password string `json:"password"` 9 | CreatedAt time.Time `json:"-"` 10 | UpdatedAt time.Time `json:"-"` 11 | } 12 | -------------------------------------------------------------------------------- /day11/models/session.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Session struct { 4 | Username string `json:"-"` 5 | Token string `json:"-"` 6 | } 7 | -------------------------------------------------------------------------------- /day11/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "tictactoe/controllers" 7 | "tictactoe/db" 8 | ) 9 | 10 | func StartServer() { 11 | playerDB := new(db.PlayerDB) 12 | sessionDB := new(db.SessionDB) 13 | gameDB := new(db.GameDB) 14 | 15 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB) 16 | gameAPI := controllers.NewGameAPI(gameDB, playerDB, sessionDB) 17 | 18 | router := http.NewServeMux() 19 | router.HandleFunc("/player/login", loginAPI.HandleLogin) 20 | router.HandleFunc("/player/signup", loginAPI.HandleSignup) 21 | router.HandleFunc("/player/token", loginAPI.HandleTokenLogin) 22 | 23 | router.HandleFunc("/game/new", gameAPI.HandleNewGame) 24 | router.HandleFunc("/game/join", gameAPI.HandleJoinGame) 25 | router.HandleFunc("/game/play", gameAPI.HandlePlayTurn) 26 | router.HandleFunc("/game/get", gameAPI.HandleGetGame) 27 | 28 | log.Println("started server at http://localhost:8080") 29 | http.ListenAndServe(":8080", router) 30 | } 31 | -------------------------------------------------------------------------------- /day11/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func CheckWinner(cells []string) string { 4 | if checkIfPlayerWon(cells, "X") { 5 | return "X" 6 | } else if checkIfPlayerWon(cells, "O") { 7 | return "O" 8 | } 9 | return "" 10 | } 11 | 12 | func checkIfPlayerWon(cells []string, playerType string) bool { 13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType 14 | 15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType 16 | 17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType 18 | 19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType 20 | 21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType 22 | 23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType 24 | 25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType 26 | 27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType 28 | 29 | return firstColumn || 30 | secondColumn || 31 | thirdColumn || 32 | firstRow || 33 | secondRow || 34 | thirdRow || 35 | firstDiagonal || 36 | secondDiagonal 37 | } 38 | -------------------------------------------------------------------------------- /day12/README.md: -------------------------------------------------------------------------------- 1 | # Day 12: 2 | 3 | ### React Router 4 | Install React's Router by running 5 | 6 | ```bash 7 | npm install react-router-dom 8 | ``` 9 | 10 | inside the client's directory 11 | 12 | 13 | ### Middleware definition example: 14 | 15 | ```go 16 | 17 | // handler is the rest api handler that we want to do a process before the request reaches it 18 | func middlewareName(handler http.HandlerFunc) http.HandlerFunc { 19 | // return a rest api handler function that has the processing 20 | // where every statement before `handler.ServeHTTP(resp, req)` is concidered as middleware processing 21 | return func(resp http.ResponseWriter, req *http.Request) { 22 | // here we're setting some required http headers so that fetch can work properly 23 | resp.Header().Set("Access-Control-Allow-Origin", "*") 24 | resp.Header().Set("Access-Control-Allow-Headers", "Accept,Content-Type,Authorization") 25 | resp.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS") 26 | resp.Header().Set("Content-Type", "application/json") 27 | 28 | if req.Method != http.MethodOptions { 29 | handler.ServeHTTP(resp, req) 30 | } 31 | } 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /day12/client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day12/client/README.md: -------------------------------------------------------------------------------- 1 | # Day 5's links: 2 | 3 | * Empty Chracter `https://emptycharacter.com/` 4 | -------------------------------------------------------------------------------- /day12/client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tic Tac Toe 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day12/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tictactoe", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-router-dom": "^6.8.2" 15 | }, 16 | "devDependencies": { 17 | "@types/react": "^18.0.27", 18 | "@types/react-dom": "^18.0.10", 19 | "@vitejs/plugin-react": "^3.1.0", 20 | "vite": "^4.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /day12/client/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day12/client/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day12/client/src/App.css -------------------------------------------------------------------------------- /day12/client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import { createBrowserRouter, RouterProvider } from "react-router-dom"; 3 | import Game from "./pages/Game"; 4 | import Login from "./pages/Login"; 5 | import Signup from "./pages/Signup"; 6 | 7 | function App() { 8 | const router = createBrowserRouter([ 9 | { path: "/", element: }, 10 | { path: "/login", element: }, 11 | { path: "/signup", element: }, 12 | ]); 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /day12/client/src/components/GameBoard.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | display: inline; 3 | } 4 | 5 | button.cell { 6 | font-size: 40px; 7 | font-weight: bold; 8 | background-color: #000000; 9 | color: #ffffff; 10 | border: 1px #ababab solid; 11 | 12 | width: 140px; 13 | height: 140px; 14 | 15 | padding: 30px; 16 | } 17 | 18 | .board { 19 | } 20 | 21 | .bigContainer { 22 | position: absolute; 23 | top: 50%; 24 | left: 50%; 25 | transform: translateX(-50%) translateY(-50%); 26 | } 27 | 28 | .playerStatus { 29 | color: #ffffff; 30 | text-align: center; 31 | } 32 | -------------------------------------------------------------------------------- /day12/client/src/components/GameBoard.jsx: -------------------------------------------------------------------------------- 1 | import GameData from "../data/GameData"; 2 | import Game from "../models/Game"; 3 | import GameRequests from "../utils/GameRequests"; 4 | import LoginRequests from "../utils/LoginRequests"; 5 | import "./GameBoard.css"; 6 | import Stats from "./Stats"; 7 | import { useEffect } from "react"; 8 | import { useState } from "react"; 9 | import Player from "../models/Player"; 10 | 11 | function GameBoard({ game, setGame }) { 12 | const [player, setPlayer] = useState(new Player()); 13 | 14 | const play = async (cellNumber) => { 15 | await GameRequests.playTurn({ 16 | cellNumber: cellNumber, 17 | gameId: game.id, 18 | }).then(async (resp) => { 19 | if (resp.ok) { 20 | setGame(new Game(await resp.json())); 21 | return; 22 | } 23 | window.alert(await resp.text()); 24 | }); 25 | }; 26 | 27 | useEffect(() => { 28 | (async () => { 29 | setPlayer(await LoginRequests.tokenLogin()); 30 | })(); 31 | 32 | const interval = setInterval(async () => { 33 | setGame(await GameRequests.getGame(game.id)); 34 | }, 1000); 35 | 36 | return () => clearInterval(interval); 37 | }, []); 38 | 39 | const getCurrentPlayer = () => { 40 | switch (game.currentPlayerId) { 41 | case game.xPlayerId: 42 | return "X"; 43 | case game.oPlayerId: 44 | return "O"; 45 | } 46 | return ""; 47 | }; 48 | 49 | const isPlayerPlaying = () => { 50 | return player.id === game.currentPlayerId; 51 | }; 52 | 53 | return ( 54 |
55 |

56 | {isPlayerPlaying() ? "Your turn" : "Wait for your opponent"} 57 |

58 |
59 | {game.cells.map((cell, index) => { 60 | return ( 61 |
62 | 71 | {(index + 1) % 3 === 0 &&
} 72 |
73 | ); 74 | })} 75 |
76 |
77 | 83 |
84 |
85 | ); 86 | } 87 | 88 | export default GameBoard; 89 | -------------------------------------------------------------------------------- /day12/client/src/components/Stats.css: -------------------------------------------------------------------------------- 1 | span { 2 | color: #ffffff; 3 | font-weight: 900; 4 | font-size: 25px; 5 | text-align: center; 6 | display: inline-block; 7 | width: 100%; 8 | } 9 | 10 | .container { 11 | display: flex; 12 | justify-content: space-between; 13 | margin-left: 20px; 14 | margin-right: 20px; 15 | margin-top: 20px; 16 | } 17 | -------------------------------------------------------------------------------- /day12/client/src/components/Stats.jsx: -------------------------------------------------------------------------------- 1 | import "./Stats.css"; 2 | 3 | function Stats({ xScore, oScore, turn, gameId }) { 4 | return ( 5 |
6 |
7 | Player X 8 |
9 | {xScore} 10 |
11 | 12 |
13 | Player O 14 |
15 | {oScore} 16 |
17 | 18 |
19 | Turn 20 |
21 | {turn} 22 |
23 | 24 |
25 | Game Id 26 |
27 | {gameId} 28 |
29 |
30 | ); 31 | } 32 | 33 | export default Stats; 34 | -------------------------------------------------------------------------------- /day12/client/src/data/GameData.js: -------------------------------------------------------------------------------- 1 | import GameLogic from "./GameLogic"; 2 | 3 | class GameData { 4 | cells; 5 | turn; 6 | xScore; 7 | oScore; 8 | 9 | constructor(copiedData) { 10 | if (copiedData !== undefined && copiedData !== null) { 11 | this.cells = copiedData.cells; 12 | this.turn = copiedData.turn; 13 | this.xScore = copiedData.xScore; 14 | this.oScore = copiedData.oScore; 15 | return; 16 | } 17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 18 | this.turn = "X"; 19 | this.xScore = 0; 20 | this.oScore = 0; 21 | } 22 | 23 | resetGame() { 24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 25 | this.turn = "X"; 26 | } 27 | 28 | markCell(cellNumber) { 29 | if (this.isCellNumberInBounds(cellNumber)) return; 30 | 31 | if (this.isCellOccupied(cellNumber)) return; 32 | 33 | switch (this.turn) { 34 | case "X": 35 | this.cells[cellNumber] = "X"; 36 | break; 37 | case "O": 38 | this.cells[cellNumber] = "O"; 39 | break; 40 | } 41 | 42 | if (this.areAllCellsOccupied()) { 43 | this.resetGame(); 44 | return; 45 | } 46 | 47 | this.switchTurn(); 48 | } 49 | 50 | isCellNumberInBounds(cellNumber) { 51 | return cellNumber < 0 || cellNumber > this.cells.length - 1; 52 | } 53 | 54 | isCellOccupied(cellNumber) { 55 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O"; 56 | } 57 | 58 | areAllCellsOccupied() { 59 | for (let i = 0; i < this.cells.length; i++) { 60 | if (!this.isCellOccupied(i)) { 61 | return false; 62 | } 63 | } 64 | return true; 65 | } 66 | 67 | switchTurn() { 68 | switch (this.turn) { 69 | case "X": 70 | this.turn = "O"; 71 | break; 72 | case "O": 73 | this.turn = "X"; 74 | break; 75 | } 76 | } 77 | 78 | checkWinner() { 79 | let winner = GameLogic.checkWinner(this.cells); 80 | switch (winner) { 81 | case "X": 82 | this.xScore++; 83 | this.resetGame(); 84 | return "X"; 85 | case "O": 86 | this.oScore++; 87 | this.resetGame(); 88 | return "O"; 89 | } 90 | } 91 | } 92 | 93 | export default GameData; 94 | -------------------------------------------------------------------------------- /day12/client/src/data/GameLogic.js: -------------------------------------------------------------------------------- 1 | class GameLogic { 2 | static checkWinner(cells) { 3 | if (this.checkIfPlayerWon(cells, "X")) { 4 | return "X"; 5 | } else if (this.checkIfPlayerWon(cells, "O")) { 6 | return "O"; 7 | } 8 | return ""; 9 | } 10 | 11 | static checkIfPlayerWon(cells, playerType) { 12 | let firstDiagonal = 13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType; 14 | 15 | let secondDiagonal = 16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType; 17 | 18 | let firstRow = 19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType; 20 | 21 | let secondRow = 22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType; 23 | 24 | let thirdRow = 25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType; 26 | 27 | let firstColumn = 28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType; 29 | 30 | let secondColumn = 31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType; 32 | 33 | let thirdColumn = 34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType; 35 | 36 | return ( 37 | firstColumn || 38 | secondColumn || 39 | thirdColumn || 40 | firstRow || 41 | secondRow || 42 | thirdRow || 43 | firstDiagonal || 44 | secondDiagonal 45 | ); 46 | } 47 | } 48 | 49 | export default GameLogic; 50 | -------------------------------------------------------------------------------- /day12/client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000000; 3 | } 4 | -------------------------------------------------------------------------------- /day12/client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day12/client/src/models/Game.js: -------------------------------------------------------------------------------- 1 | class Game { 2 | id; 3 | xPlayerId; 4 | oPlayerId; 5 | xScore; 6 | oScore; 7 | cells; 8 | currentPlayerId; 9 | 10 | constructor(copiedGame) { 11 | if (copiedGame) { 12 | this.id = copiedGame.id; 13 | this.xPlayerId = copiedGame.xPlayerId; 14 | this.oPlayerId = copiedGame.oPlayerId; 15 | this.xScore = copiedGame.xScore; 16 | this.oScore = copiedGame.oScore; 17 | this.cells = copiedGame.cells.split(" "); 18 | this.currentPlayerId = copiedGame.currentPlayerId; 19 | return; 20 | } 21 | 22 | this.id = 0; 23 | this.xPlayerId = 0; 24 | this.oPlayerId = 0; 25 | this.xScore = 0; 26 | this.oScore = 0; 27 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 28 | this.currentPlayerId = 0; 29 | } 30 | } 31 | 32 | export default Game; 33 | -------------------------------------------------------------------------------- /day12/client/src/models/Player.js: -------------------------------------------------------------------------------- 1 | class Player { 2 | id; 3 | username; 4 | password; 5 | 6 | constructor(copiedPlayer) { 7 | if (copiedPlayer) { 8 | this.id = copiedPlayer.id; 9 | this.username = copiedPlayer.username; 10 | this.password = copiedPlayer.password; 11 | return; 12 | } 13 | this.id = 0; 14 | this.username = ""; 15 | this.password = ""; 16 | } 17 | } 18 | 19 | export default Player; 20 | -------------------------------------------------------------------------------- /day12/client/src/pages/Game.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import GameRequests from "../utils/GameRequests"; 3 | import { default as GameData } from "../models/Game"; 4 | import GameBoard from "../components/GameBoard"; 5 | import LoginRequests from "../utils/LoginRequests"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | function Game() { 9 | const [gameData, setGameData] = useState(new GameData()); 10 | 11 | const [joinGameId, setJoinGameId] = useState(0); 12 | 13 | const navigate = useNavigate(); 14 | 15 | useEffect(() => { 16 | (async () => { 17 | const player = await LoginRequests.tokenLogin(); 18 | if (!player || !player.id) { 19 | navigate("/login"); 20 | } 21 | })(); 22 | }, []); 23 | 24 | const joinGameIdOnChangeHandler = (event) => { 25 | setJoinGameId(parseInt(event.target.value)); 26 | }; 27 | 28 | const joinGame = async () => { 29 | setGameData(await GameRequests.joinGame(joinGameId)); 30 | }; 31 | 32 | const newGame = async () => { 33 | setGameData(await GameRequests.newGame()); 34 | }; 35 | 36 | return ( 37 |
38 | {gameData.id === 0 && ( 39 |
40 | 46 | 47 |
48 | 49 |
50 | )} 51 | {gameData.id !== 0 && ( 52 |
53 | 54 |
55 | )} 56 |
57 | ); 58 | } 59 | 60 | export default Game; 61 | -------------------------------------------------------------------------------- /day12/client/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Player from "../models/Player"; 3 | import LoginRequests from "../utils/LoginRequests"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | function Login() { 7 | const navigate = useNavigate(); 8 | const [player, setPlayer] = useState(new Player()); 9 | 10 | const usernameOnChangeHandler = (event) => { 11 | player.username = event.target.value; 12 | setPlayer(new Player(player)); 13 | }; 14 | 15 | const passwordOnChangeHandler = (event) => { 16 | player.password = event.target.value; 17 | setPlayer(new Player(player)); 18 | }; 19 | 20 | const login = async () => { 21 | await LoginRequests.login(player); 22 | console.log(player); 23 | navigate("/"); 24 | }; 25 | 26 | return ( 27 |
28 | 34 |
35 | 41 |
42 | 43 |
44 | ); 45 | } 46 | 47 | export default Login; 48 | -------------------------------------------------------------------------------- /day12/client/src/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Player from "../models/Player"; 3 | import LoginRequests from "../utils/LoginRequests"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | function Signup() { 7 | const navigate = useNavigate(); 8 | const [player, setPlayer] = useState(new Player()); 9 | 10 | const usernameOnChangeHandler = (event) => { 11 | player.username = event.target.value; 12 | setPlayer(new Player(player)); 13 | }; 14 | 15 | const passwordOnChangeHandler = (event) => { 16 | player.password = event.target.value; 17 | setPlayer(new Player(player)); 18 | }; 19 | 20 | const signup = async () => { 21 | await LoginRequests.signup(player); 22 | console.log(player); 23 | navigate("/login"); 24 | }; 25 | 26 | return ( 27 |
28 | 34 |
35 | 41 |
42 | 43 |
44 | ); 45 | } 46 | 47 | export default Signup; 48 | -------------------------------------------------------------------------------- /day12/client/src/utils/GameRequests.js: -------------------------------------------------------------------------------- 1 | import Game from "../models/Game"; 2 | 3 | class GameRequests { 4 | static async newGame() { 5 | return await fetch("http://localhost:8080/game/new", { 6 | method: "GET", 7 | mode: "cors", 8 | headers: { 9 | Authorization: localStorage.getItem("token"), 10 | }, 11 | }) 12 | .then((resp) => { 13 | return resp.json(); 14 | }) 15 | .then((game) => { 16 | return new Game(game); 17 | }) 18 | .catch((err) => console.log(err)); 19 | } 20 | 21 | static async joinGame(gameId) { 22 | return await fetch(`http://localhost:8080/game/join?id=${gameId}`, { 23 | method: "GET", 24 | mode: "cors", 25 | headers: { 26 | Authorization: localStorage.getItem("token"), 27 | }, 28 | }) 29 | .then((resp) => { 30 | return resp.json(); 31 | }) 32 | .then((game) => { 33 | return new Game(game); 34 | }) 35 | .catch((err) => console.log(err)); 36 | } 37 | 38 | static async playTurn(reqBody) { 39 | return fetch("http://localhost:8080/game/play", { 40 | method: "POST", 41 | mode: "cors", 42 | headers: { 43 | Authorization: localStorage.getItem("token"), 44 | }, 45 | body: JSON.stringify(reqBody), 46 | }); 47 | } 48 | 49 | static async getGame(id) { 50 | return await fetch(`http://localhost:8080/game/get?id=${id}`, { 51 | method: "GET", 52 | mode: "cors", 53 | headers: { 54 | Authorization: localStorage.getItem("token"), 55 | }, 56 | }) 57 | .then((resp) => { 58 | return resp.json(); 59 | }) 60 | .then((game) => { 61 | return new Game(game); 62 | }) 63 | .catch((err) => console.log(err)); 64 | } 65 | } 66 | 67 | export default GameRequests; 68 | -------------------------------------------------------------------------------- /day12/client/src/utils/LoginRequests.js: -------------------------------------------------------------------------------- 1 | import Player from "../models/Player"; 2 | 3 | class LoginRequests { 4 | static async login(player) { 5 | return await fetch("http://localhost:8080/player/login", { 6 | method: "POST", 7 | mode: "cors", 8 | body: JSON.stringify(player), 9 | }) 10 | .then((resp) => { 11 | return resp.json(); 12 | }) 13 | .then((data) => { 14 | localStorage.setItem("token", data["token"]); 15 | }) 16 | .catch((err) => console.log(err)); 17 | } 18 | 19 | static async signup(player) { 20 | return await fetch("http://localhost:8080/player/signup", { 21 | method: "POST", 22 | mode: "cors", 23 | body: JSON.stringify(player), 24 | }) 25 | .then((resp) => {}) 26 | .catch((err) => console.log(err)); 27 | } 28 | 29 | static async tokenLogin() { 30 | return await fetch("http://localhost:8080/player/token", { 31 | method: "GET", 32 | mode: "cors", 33 | headers: { Authorization: localStorage.getItem("token") }, 34 | }) 35 | .then((resp) => { 36 | return resp.json(); 37 | }) 38 | .then((player) => { 39 | return new Player(player); 40 | }) 41 | .catch((err) => console.log(err)); 42 | } 43 | } 44 | 45 | export default LoginRequests; 46 | -------------------------------------------------------------------------------- /day12/client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day12/server/controllers/game_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "tictactoe/db" 9 | "tictactoe/models" 10 | ) 11 | 12 | type GameAPI struct { 13 | gameDB *db.GameDB 14 | playerDB *db.PlayerDB 15 | sessionDB *db.SessionDB 16 | } 17 | 18 | func NewGameAPI(gameDB *db.GameDB, playerDB *db.PlayerDB, sessionDB *db.SessionDB) *GameAPI { 19 | return &GameAPI{ 20 | gameDB: gameDB, 21 | playerDB: playerDB, 22 | sessionDB: sessionDB, 23 | } 24 | } 25 | 26 | func (g *GameAPI) HandleNewGame(resp http.ResponseWriter, req *http.Request) { 27 | token := req.Header.Get("Authorization") 28 | if len(token) == 0 { 29 | resp.WriteHeader(http.StatusUnauthorized) 30 | return 31 | } 32 | 33 | player, err := g.fetchPlayerUsingToken(token) 34 | if err != nil { 35 | resp.WriteHeader(http.StatusUnauthorized) 36 | return 37 | } 38 | 39 | game := models.Game{ 40 | XPlayerId: player.Id, 41 | } 42 | 43 | newGame, err := g.gameDB.Create(game) 44 | if err != nil { 45 | resp.WriteHeader(http.StatusInternalServerError) 46 | return 47 | } 48 | 49 | json.NewEncoder(resp).Encode(newGame) 50 | } 51 | 52 | func (g *GameAPI) HandleJoinGame(resp http.ResponseWriter, req *http.Request) { 53 | gameId := req.URL.Query().Get("id") 54 | if len(gameId) == 0 { 55 | resp.WriteHeader(http.StatusBadRequest) 56 | return 57 | } 58 | 59 | intGameId, err := strconv.Atoi(gameId) 60 | if len(gameId) == 0 { 61 | resp.WriteHeader(http.StatusBadRequest) 62 | return 63 | } 64 | 65 | token := req.Header.Get("Authorization") 66 | if len(token) == 0 { 67 | resp.WriteHeader(http.StatusUnauthorized) 68 | return 69 | } 70 | 71 | player, err := g.fetchPlayerUsingToken(token) 72 | if err != nil { 73 | resp.WriteHeader(http.StatusUnauthorized) 74 | return 75 | } 76 | 77 | game, err := g.gameDB.Get(intGameId) 78 | if err != nil { 79 | fmt.Println(err) 80 | resp.WriteHeader(http.StatusNotFound) 81 | return 82 | } 83 | 84 | if game.OPlayerId != 0 { 85 | resp.WriteHeader(http.StatusBadRequest) 86 | fmt.Fprintln(resp, "THIS GAME IS FULLY OCCUPIED!!!") 87 | return 88 | } 89 | 90 | game.OPlayerId = player.Id 91 | 92 | err = g.gameDB.Update(game.Id, game) 93 | if err != nil { 94 | resp.WriteHeader(http.StatusInternalServerError) 95 | return 96 | } 97 | 98 | json.NewEncoder(resp).Encode(game) 99 | } 100 | 101 | func (g *GameAPI) HandlePlayTurn(resp http.ResponseWriter, req *http.Request) { 102 | var reqBody struct { 103 | GameId int `json:"gameId"` 104 | CellNumber int `json:"cellNumber"` 105 | } 106 | 107 | err := json.NewDecoder(req.Body).Decode(&reqBody) 108 | if err != nil { 109 | resp.WriteHeader(http.StatusBadRequest) 110 | return 111 | } 112 | 113 | token := req.Header.Get("Authorization") 114 | if len(token) == 0 { 115 | resp.WriteHeader(http.StatusUnauthorized) 116 | return 117 | } 118 | 119 | player, err := g.fetchPlayerUsingToken(token) 120 | if err != nil { 121 | resp.WriteHeader(http.StatusUnauthorized) 122 | return 123 | } 124 | 125 | game, err := g.gameDB.Get(reqBody.GameId) 126 | if err != nil { 127 | resp.WriteHeader(http.StatusInternalServerError) 128 | return 129 | } 130 | 131 | if player.Id != game.CurrentPlayerId { 132 | resp.WriteHeader(http.StatusBadRequest) 133 | fmt.Fprintln(resp, "this player can't play right now") 134 | return 135 | } 136 | 137 | err = game.MarkCell(reqBody.CellNumber) 138 | if err != nil { 139 | resp.WriteHeader(http.StatusBadRequest) 140 | fmt.Fprintln(resp, err.Error()) 141 | return 142 | } 143 | 144 | game.CheckWinnerAndUpdateScore() 145 | 146 | err = g.gameDB.Update(game.Id, game) 147 | if err != nil { 148 | resp.WriteHeader(http.StatusInternalServerError) 149 | return 150 | } 151 | 152 | json.NewEncoder(resp).Encode(game) 153 | } 154 | 155 | func (g *GameAPI) HandleGetGame(resp http.ResponseWriter, req *http.Request) { 156 | gameId := req.URL.Query().Get("id") 157 | if len(gameId) == 0 { 158 | resp.WriteHeader(http.StatusBadRequest) 159 | return 160 | } 161 | 162 | intGameId, err := strconv.Atoi(gameId) 163 | if len(gameId) == 0 { 164 | resp.WriteHeader(http.StatusBadRequest) 165 | return 166 | } 167 | 168 | token := req.Header.Get("Authorization") 169 | if len(token) == 0 { 170 | resp.WriteHeader(http.StatusUnauthorized) 171 | return 172 | } 173 | 174 | player, err := g.fetchPlayerUsingToken(token) 175 | if err != nil { 176 | resp.WriteHeader(http.StatusUnauthorized) 177 | return 178 | } 179 | 180 | game, err := g.gameDB.Get(intGameId) 181 | if err != nil { 182 | resp.WriteHeader(http.StatusInternalServerError) 183 | return 184 | } 185 | 186 | if player.Id != game.XPlayerId && player.Id != game.OPlayerId { 187 | resp.WriteHeader(http.StatusUnauthorized) 188 | fmt.Fprintln(resp, "this player does not belong to this game!") 189 | return 190 | } 191 | 192 | json.NewEncoder(resp).Encode(game) 193 | } 194 | 195 | func (g *GameAPI) fetchPlayerUsingToken(token string) (models.Player, error) { 196 | session, err := g.sessionDB.Get(token) 197 | if err != nil { 198 | return models.Player{}, err 199 | } 200 | 201 | player, err := g.playerDB.Get(session.Username) 202 | if err != nil { 203 | return models.Player{}, err 204 | } 205 | 206 | return player, nil 207 | } 208 | -------------------------------------------------------------------------------- /day12/server/controllers/login_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "tictactoe/db" 9 | "tictactoe/models" 10 | 11 | "github.com/google/uuid" 12 | "golang.org/x/crypto/bcrypt" 13 | ) 14 | 15 | type LoginAPI struct { 16 | playerDB *db.PlayerDB 17 | sessionDB *db.SessionDB 18 | } 19 | 20 | func NewLoginAPI(playerDB *db.PlayerDB, sessionDB *db.SessionDB) *LoginAPI { 21 | return &LoginAPI{ 22 | playerDB: playerDB, 23 | sessionDB: sessionDB, 24 | } 25 | } 26 | 27 | type reqBody struct { 28 | Username string `json:"username"` 29 | Password string `json:"password"` 30 | } 31 | 32 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) { 33 | var requestBody reqBody 34 | 35 | err := json.NewDecoder(req.Body).Decode(&requestBody) 36 | if err != nil { 37 | resp.WriteHeader(http.StatusBadRequest) 38 | return 39 | } 40 | 41 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost) 42 | if err != nil { 43 | resp.WriteHeader(http.StatusInternalServerError) 44 | return 45 | } 46 | 47 | requestBody.Password = string(hashed) 48 | 49 | player, err := l.playerDB.Create(models.Player{ 50 | Username: requestBody.Username, 51 | Password: requestBody.Password, 52 | }) 53 | if err != nil { 54 | resp.WriteHeader(http.StatusInternalServerError) 55 | return 56 | } 57 | 58 | player.Password = "" 59 | 60 | json.NewEncoder(resp).Encode(player) 61 | } 62 | 63 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) { 64 | var requestBody reqBody 65 | 66 | err := json.NewDecoder(req.Body).Decode(&requestBody) 67 | if err != nil { 68 | resp.WriteHeader(http.StatusBadRequest) 69 | fmt.Fprintln(resp, "👎👎 your request has an incorrect form 👎👎") 70 | return 71 | } 72 | 73 | player, err := l.playerDB.Get(requestBody.Username) 74 | if err != nil { 75 | resp.WriteHeader(http.StatusUnauthorized) 76 | fmt.Fprintln(resp, "username is incorrect 👎") 77 | return 78 | } 79 | 80 | err = bcrypt.CompareHashAndPassword([]byte(player.Password), []byte(requestBody.Password)) 81 | if err != nil { 82 | resp.WriteHeader(http.StatusUnauthorized) 83 | fmt.Fprintln(resp, "password is incorrect 👎") 84 | return 85 | } 86 | 87 | session := models.Session{ 88 | Username: player.Username, 89 | Token: strings.ReplaceAll(uuid.NewString(), "-", ""), 90 | } 91 | 92 | err = l.sessionDB.Create(session) 93 | if err != nil { 94 | resp.WriteHeader(http.StatusInternalServerError) 95 | fmt.Fprintln(resp, "something went wrong ☹️") 96 | return 97 | } 98 | 99 | json.NewEncoder(resp).Encode(map[string]any{ 100 | "token": session.Token, 101 | }) 102 | } 103 | 104 | func (l *LoginAPI) HandleTokenLogin(resp http.ResponseWriter, req *http.Request) { 105 | token := req.Header.Get("Authorization") 106 | 107 | session, err := l.sessionDB.Get(token) 108 | if err != nil { 109 | resp.WriteHeader(http.StatusUnauthorized) 110 | fmt.Fprintln(resp, "invalid token 👎") 111 | return 112 | } 113 | 114 | player, err := l.playerDB.Get(session.Username) 115 | if err != nil { 116 | resp.WriteHeader(http.StatusInternalServerError) 117 | fmt.Fprintln(resp, "something went wrong") 118 | return 119 | } 120 | 121 | player.Password = "" 122 | 123 | resp.Header().Set("Content-Type", "application/json") 124 | json.NewEncoder(resp).Encode(player) 125 | } 126 | -------------------------------------------------------------------------------- /day12/server/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var connectionInstance *sql.DB = nil 10 | 11 | func GetInstance() *sql.DB { 12 | if connectionInstance == nil { 13 | var err error 14 | // DB_USERNAME is root by default 15 | // DB_PASSWORD is what you've set when installing the database 16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName` 17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True") 18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True") 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | return connectionInstance 24 | } 25 | -------------------------------------------------------------------------------- /day12/server/db/game_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type GameDB struct{} 6 | 7 | func (g *GameDB) Create(game models.Game) (models.Game, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.games (x_player_id, o_player_id, x_score, o_score, current_player_id, cells) VALUES (?, ?, ?, ?, ?, ?)", 10 | game.XPlayerId, 0, 0, 0, game.XPlayerId, "1 2 3 4 5 6 7 8 9") 11 | if err != nil { 12 | return models.Game{}, err 13 | } 14 | 15 | lastId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Game{}, err 18 | } 19 | 20 | game.Id = int(lastId) 21 | game.Cells = "1 2 3 4 5 6 7 8 9" 22 | game.CurrentPlayerId = game.XPlayerId 23 | game.XScore = 0 24 | game.OScore = 0 25 | 26 | return game, nil 27 | } 28 | 29 | func (g *GameDB) Get(id int) (models.Game, error) { 30 | var game models.Game 31 | err := GetInstance(). 32 | QueryRow("SELECT * FROM ttt.games WHERE id=?", id). 33 | Scan(&game.Id, &game.XPlayerId, &game.OPlayerId, &game.XScore, &game.OScore, 34 | &game.Cells, &game.CurrentPlayerId) 35 | if err != nil { 36 | return models.Game{}, err 37 | } 38 | 39 | return game, nil 40 | } 41 | 42 | func (p *GameDB) Update(id int, game models.Game) error { 43 | _, err := GetInstance().Exec( 44 | "UPDATE ttt.games SET cells=?, x_score=?, o_score=?, current_player_id=?, o_player_id=?", 45 | game.Cells, game.XScore, game.OScore, game.CurrentPlayerId, game.OPlayerId) 46 | if err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func (p *GameDB) Delete(id int) error { 53 | _, err := GetInstance().Exec("DELETE FROM ttt.games WHERE id=?", id) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /day12/server/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ttt; 2 | 3 | CREATE TABLE IF NOT EXISTS ttt.players ( 4 | id INTEGER AUTO_INCREMENT, 5 | username VARCHAR(60) UNIQUE NOT NULL, 6 | password VARCHAR(200), 7 | created_at TIMESTAMP, 8 | updated_at TIMESTAMP, 9 | PRIMARY KEY (id) 10 | ); 11 | 12 | CREATE TABLE IF NOT EXISTS ttt.sessions ( 13 | id INTEGER AUTO_INCREMENT, 14 | username VARCHAR(60), 15 | token VARCHAR(32) UNIQUE, 16 | PRIMARY KEY (id) 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS ttt.game ( 20 | id INTEGER AUTO_INCREMENT, 21 | x_player_id INTEGER, 22 | o_player_id INTEGER, 23 | x_score INTEGER, 24 | o_score INTEGER, 25 | cells VARCHAR(20), 26 | current_player_id INTEGER, 27 | PRIMARY KEY (id), 28 | FOREIGN KEY (x_player_id) REFERENCES ttt.players(id), 29 | FOREIGN KEY (current_player_id) REFERENCES ttt.players(id) 30 | ); 31 | -------------------------------------------------------------------------------- /day12/server/db/player_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type PlayerDB struct{} 6 | 7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)", 10 | player.Username, player.Password) 11 | if err != nil { 12 | return models.Player{}, err 13 | } 14 | 15 | playerId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Player{}, err 18 | } 19 | 20 | player.Id = int(playerId) 21 | 22 | return player, nil 23 | } 24 | 25 | func (p *PlayerDB) Get(username string) (models.Player, error) { 26 | var player models.Player 27 | err := GetInstance(). 28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username). 29 | Scan(&player.Id, &player.Username, &player.Password) 30 | 31 | if err != nil { 32 | return models.Player{}, err 33 | } 34 | 35 | return player, nil 36 | } 37 | 38 | func (p *PlayerDB) Update(username string, player models.Player) error { 39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (p *PlayerDB) Delete(username string) error { 47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } -------------------------------------------------------------------------------- /day12/server/db/session_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type SessionDB struct{} 6 | 7 | func (s *SessionDB) Create(session models.Session) error { 8 | _, err := GetInstance().Exec("INSERT INTO ttt.sessions (username, token) VALUES (?, ?)", session.Username, session.Token) 9 | if err != nil { 10 | return err 11 | } 12 | return nil 13 | } 14 | 15 | func (s *SessionDB) Get(token string) (models.Session, error) { 16 | var session models.Session 17 | 18 | err := GetInstance(). 19 | QueryRow("SELECT username, token FROM ttt.sessions WHERE token=?", token). 20 | Scan(&session.Username, &session.Token) 21 | if err != nil { 22 | return models.Session{}, err 23 | } 24 | 25 | return session, nil 26 | } 27 | -------------------------------------------------------------------------------- /day12/server/go.mod: -------------------------------------------------------------------------------- 1 | module tictactoe 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.0 // indirect 7 | github.com/google/uuid v1.3.0 // indirect 8 | golang.org/x/crypto v0.6.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /day12/server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 6 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 7 | -------------------------------------------------------------------------------- /day12/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "tictactoe/router" 4 | 5 | func main() { 6 | router.StartServer() 7 | } 8 | -------------------------------------------------------------------------------- /day12/server/models/game.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "tictactoe/utils" 8 | ) 9 | 10 | type Game struct { 11 | Id int `json:"id"` 12 | XPlayerId int `json:"xPlayerId"` 13 | OPlayerId int `json:"oPlayerId"` 14 | XScore int `json:"xScore"` 15 | OScore int `json:"oScore"` 16 | Cells string `json:"cells"` 17 | CurrentPlayerId int `json:"currentPlayerId"` 18 | } 19 | 20 | func (g *Game) resetGame() { 21 | g.Cells = "1 2 3 4 5 6 7 8 9" 22 | g.CurrentPlayerId = g.XPlayerId 23 | } 24 | 25 | func (g *Game) switchTurns() { 26 | if g.CurrentPlayerId == g.XPlayerId { 27 | g.CurrentPlayerId = g.OPlayerId 28 | } else if g.CurrentPlayerId == g.OPlayerId { 29 | g.CurrentPlayerId = g.XPlayerId 30 | } 31 | } 32 | 33 | func (g *Game) MarkCell(cellNumber int) error { 34 | if !g.isCellNumberInBounds(cellNumber) { 35 | return errors.New("cell number is not in bound") 36 | } 37 | 38 | cells := strings.Split(g.Cells, " ") 39 | if g.areAllCellsOccupied(cells) { 40 | return errors.New("all game's cells are occupied") 41 | } 42 | 43 | if g.isCellOccupied(cells, cellNumber) { 44 | return errors.New("the requested cell: " + fmt.Sprint(cellNumber) + " is occupied with: " + cells[cellNumber]) 45 | } 46 | 47 | currentPlayer := g.getCurrentPlayerSymbol() 48 | cells[cellNumber] = currentPlayer 49 | g.Cells = strings.Join(cells, " ") 50 | 51 | g.switchTurns() 52 | 53 | return nil 54 | } 55 | 56 | func (g *Game) CheckWinnerAndUpdateScore() { 57 | cells := strings.Split(g.Cells, " ") 58 | 59 | if g.areAllCellsOccupied(cells) { 60 | g.resetGame() 61 | return 62 | } 63 | 64 | winner := utils.CheckWinner(cells) 65 | switch winner { 66 | case "X": 67 | g.XScore++ 68 | case "O": 69 | g.OScore++ 70 | } 71 | 72 | if len(winner) > 0 { 73 | g.resetGame() 74 | } 75 | } 76 | 77 | func (g *Game) isCellOccupied(cells []string, cellNumber int) bool { 78 | return cells[cellNumber] == "X" || cells[cellNumber] == "O" 79 | } 80 | 81 | func (g *Game) areAllCellsOccupied(cells []string) bool { 82 | for index, _ := range cells { 83 | if !g.isCellOccupied(cells, index) { 84 | return false 85 | } 86 | } 87 | return true 88 | } 89 | 90 | func (g *Game) isCellNumberInBounds(cellNumber int) bool { 91 | return cellNumber < 9 && cellNumber >= 0 92 | } 93 | 94 | func (g *Game) getCurrentPlayerSymbol() string { 95 | if g.CurrentPlayerId == g.XPlayerId { 96 | return "X" 97 | } else if g.CurrentPlayerId == g.OPlayerId { 98 | return "O" 99 | } 100 | return "" 101 | } 102 | -------------------------------------------------------------------------------- /day12/server/models/player.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Player struct { 6 | Id int `json:"id"` 7 | Username string `json:"username"` 8 | Password string `json:"password"` 9 | CreatedAt time.Time `json:"-"` 10 | UpdatedAt time.Time `json:"-"` 11 | } 12 | -------------------------------------------------------------------------------- /day12/server/models/session.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Session struct { 4 | Username string `json:"-"` 5 | Token string `json:"-"` 6 | } 7 | -------------------------------------------------------------------------------- /day12/server/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "tictactoe/controllers" 7 | "tictactoe/db" 8 | ) 9 | 10 | func ignoreOptionsMethod(handler http.HandlerFunc) http.HandlerFunc { 11 | return func(resp http.ResponseWriter, req *http.Request) { 12 | resp.Header().Set("Access-Control-Allow-Origin", "*") 13 | resp.Header().Set("Access-Control-Allow-Headers", "Accept,Content-Type,Authorization") 14 | resp.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS") 15 | resp.Header().Set("Content-Type", "application/json") 16 | 17 | if req.Method != http.MethodOptions { 18 | handler.ServeHTTP(resp, req) 19 | } 20 | } 21 | } 22 | 23 | func StartServer() { 24 | playerDB := new(db.PlayerDB) 25 | sessionDB := new(db.SessionDB) 26 | gameDB := new(db.GameDB) 27 | 28 | loginAPI := controllers.NewLoginAPI(playerDB, sessionDB) 29 | gameAPI := controllers.NewGameAPI(gameDB, playerDB, sessionDB) 30 | 31 | router := http.NewServeMux() 32 | router.HandleFunc("/player/login", ignoreOptionsMethod(loginAPI.HandleLogin)) 33 | router.HandleFunc("/player/signup", ignoreOptionsMethod(loginAPI.HandleSignup)) 34 | router.HandleFunc("/player/token", ignoreOptionsMethod(loginAPI.HandleTokenLogin)) 35 | 36 | router.HandleFunc("/game/new", ignoreOptionsMethod(gameAPI.HandleNewGame)) 37 | router.HandleFunc("/game/join", ignoreOptionsMethod(gameAPI.HandleJoinGame)) 38 | router.HandleFunc("/game/play", ignoreOptionsMethod(gameAPI.HandlePlayTurn)) 39 | router.HandleFunc("/game/get", ignoreOptionsMethod(gameAPI.HandleGetGame)) 40 | 41 | log.Println("started server at http://localhost:8080") 42 | http.ListenAndServe(":8080", router) 43 | } 44 | -------------------------------------------------------------------------------- /day12/server/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func CheckWinner(cells []string) string { 4 | if checkIfPlayerWon(cells, "X") { 5 | return "X" 6 | } else if checkIfPlayerWon(cells, "O") { 7 | return "O" 8 | } 9 | return "" 10 | } 11 | 12 | func checkIfPlayerWon(cells []string, playerType string) bool { 13 | var firstDiagonal = cells[0] == cells[4] && cells[4] == cells[8] && cells[0] == playerType 14 | 15 | var secondDiagonal = cells[2] == cells[4] && cells[4] == cells[6] && cells[2] == playerType 16 | 17 | var firstRow = cells[0] == cells[1] && cells[1] == cells[2] && cells[1] == playerType 18 | 19 | var secondRow = cells[3] == cells[4] && cells[4] == cells[5] && cells[3] == playerType 20 | 21 | var thirdRow = cells[6] == cells[7] && cells[7] == cells[8] && cells[6] == playerType 22 | 23 | var firstColumn = cells[0] == cells[3] && cells[3] == cells[6] && cells[0] == playerType 24 | 25 | var secondColumn = cells[1] == cells[4] && cells[4] == cells[7] && cells[1] == playerType 26 | 27 | var thirdColumn = cells[2] == cells[5] && cells[5] == cells[8] && cells[8] == playerType 28 | 29 | return firstColumn || 30 | secondColumn || 31 | thirdColumn || 32 | firstRow || 33 | secondRow || 34 | thirdRow || 35 | firstDiagonal || 36 | secondDiagonal 37 | } 38 | -------------------------------------------------------------------------------- /day2/README.md: -------------------------------------------------------------------------------- 1 | # Day 2 2 | 3 | ## Create React Project 4 | 1. Create skeleton project 5 | 6 | ```bash 7 | npm create vite@latest PROJECT_NAME -- --template react 8 | ``` 9 | 10 | 2. Install project's dependencies 11 | 12 | ```bash 13 | cd PROJECT_NAME 14 | npm install 15 | ``` 16 | 17 | 3. Run the project 18 | ```bash 19 | npm run dev 20 | ``` 21 | 22 | 4. Open `localhost:5173` in your web browser 23 | -------------------------------------------------------------------------------- /day2/javascript/01-classes-and-objects.js: -------------------------------------------------------------------------------- 1 | let obj = { 2 | name: "Jaber", 3 | phone: "+962791234567", 4 | }; 5 | 6 | console.log("whole object", obj); 7 | 8 | console.log("name", obj.name); 9 | 10 | obj.name = "Ali"; 11 | console.log("name", obj.name); 12 | 13 | class Student { 14 | id; 15 | name; 16 | gpa; 17 | courses; 18 | /** 19 | * @param{string} id 20 | * @param{string} name 21 | * @param{number} gpa 22 | * @param{Array} courses 23 | */ 24 | constructor (id, name, gpa, courses) { 25 | this.id = id; 26 | this.name = name; 27 | this.gpa = gpa; 28 | this.courses = courses; 29 | } 30 | } 31 | let ali = new Student("1", "Ali", 90, ["C++", "Discrete"]); 32 | let jaber = new Student("2", "Jaber", 70, ["C++", "Discrete", "English"]); 33 | 34 | console.log(ali); 35 | console.log(jaber); 36 | -------------------------------------------------------------------------------- /day2/javascript/02-module1.mjs: -------------------------------------------------------------------------------- 1 | export function exp2(power) { 2 | return 1 << power; 3 | } 4 | 5 | export default class Car { 6 | model; 7 | speed; 8 | constructor(model, speed) { 9 | this.model = model; 10 | this.speed = speed; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /day2/javascript/03-module2.mjs: -------------------------------------------------------------------------------- 1 | import Car, { exp2 } from "./module1.mjs"; 2 | 3 | console.log(exp2(3)); 4 | 5 | const nissan = new Car("Nissan", 200); 6 | console.log(nissan); 7 | -------------------------------------------------------------------------------- /day2/react/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day2/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day2/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tectactoe_", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /day2/react/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day2/react/src/App.jsx: -------------------------------------------------------------------------------- 1 | import Hello from "./components/Hello"; 2 | import HelloProps from "./components/HelloProps"; 3 | 4 | function App() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /day2/react/src/components/Hello.jsx: -------------------------------------------------------------------------------- 1 | export default function Hello() { 2 | return ( 3 |
4 |

Hello, World

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /day2/react/src/components/HelloProps.jsx: -------------------------------------------------------------------------------- 1 | export default function HelloProps({ name }) { 2 | return ( 3 |
4 |

Hello, {name}

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /day2/react/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day2/react/src/index.css -------------------------------------------------------------------------------- /day2/react/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day2/react/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day3/README.md: -------------------------------------------------------------------------------- 1 | # Day 3 2 | 3 | ### Prettier extention for code formatting 4 | 5 | `https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode` 6 | 7 | then press `Alt+Shift+f` in VS Code when you need to format your code. 8 | -------------------------------------------------------------------------------- /day3/part1/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day3/part1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day3/part1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tectactoe_", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /day3/part1/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day3/part1/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import YouShallPass from "./components/YouShallPass"; 3 | 4 | function App() { 5 | const [pass, setPass] = useState(false); 6 | 7 | const togglePass = () => { 8 | setPass(!pass); 9 | }; 10 | 11 | return ( 12 |
13 | 14 |
15 | {pass && } 16 |
17 | ); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /day3/part1/src/components/YouShallPass.jsx: -------------------------------------------------------------------------------- 1 | export default function YouShallPass() { 2 | return ( 3 |
4 |

You Shall Pass!

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /day3/part1/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part1/src/index.css -------------------------------------------------------------------------------- /day3/part1/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day3/part1/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day3/part2/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day3/part2/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day3/part2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tectactoe_", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /day3/part2/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day3/part2/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function App() { 4 | const [counter, setCounter] = useState(0); 5 | 6 | const increaseCounter = () => { 7 | setCounter(counter + 1); 8 | }; 9 | 10 | return ( 11 |
12 |

Your counter is {counter}

13 | 14 |
15 | ); 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /day3/part2/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part2/src/index.css -------------------------------------------------------------------------------- /day3/part2/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day3/part2/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day3/part3/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day3/part3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day3/part3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tectactoe_", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /day3/part3/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day3/part3/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function App() { 4 | const [list, setList] = useState([]); 5 | const [todo, setTodo] = useState(""); 6 | 7 | const addTodo = () => { 8 | list.push(todo); 9 | setTodo(""); 10 | }; 11 | 12 | const onChangeHandler = (e) => { 13 | setTodo(e.target.value); 14 | }; 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 |
22 |
    23 | {list.map((todo) => { 24 | return
  • {todo}
  • ; 25 | })} 26 |
27 |
28 | ); 29 | } 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /day3/part3/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day3/part3/src/index.css -------------------------------------------------------------------------------- /day3/part3/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day3/part3/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day4/students/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day4/students/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Intro 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day4/students/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } -------------------------------------------------------------------------------- /day4/students/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day4/students/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/students/src/App.css -------------------------------------------------------------------------------- /day4/students/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import "./App.css"; 3 | import Student from "./classes/Student"; 4 | import Students from "./componets/Students"; 5 | 6 | function App() { 7 | const [arr, setStudents] = useState([ 8 | new Student("1", "Ali", 90), 9 | new Student("2", "Jaber", 70), 10 | new Student("3", "Omar", 80.3), 11 | ]); 12 | 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /day4/students/src/classes/Student.js: -------------------------------------------------------------------------------- 1 | class Student { 2 | id; 3 | name; 4 | gpa; 5 | 6 | constructor (id, name, gpa) { 7 | this.id = id; 8 | this.name = name; 9 | this.gpa = gpa; 10 | } 11 | } 12 | 13 | export default Student; -------------------------------------------------------------------------------- /day4/students/src/componets/Students.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import Student from "../classes/Student"; 3 | 4 | function Students({ students, setStudents }) { 5 | const [id, setId] = useState(""); 6 | const [name, setName] = useState(""); 7 | const [gpa, setGpa] = useState(0.0); 8 | 9 | const idOnchangeHandler = (e) => { 10 | setId(e.target.value); 11 | }; 12 | 13 | const nameOnchangeHandler = (e) => { 14 | setName(e.target.value); 15 | }; 16 | 17 | const gpaOnchangeHandler = (e) => { 18 | setGpa(parseFloat(e.target.value)); 19 | }; 20 | 21 | const addStudent = () => { 22 | students.push(new Student(id, name, gpa)); 23 | setStudents(students.flat()); 24 | }; 25 | 26 | const removeStudent = (id) => { 27 | const newArr = students.filter((el) => { 28 | return el.id !== id; 29 | }); 30 | 31 | // const newArr = []; 32 | // for (let i = 0; i < students.length; i++) { 33 | // if (students[i].id !== id) { 34 | // newArr.push(students[i]); 35 | // } 36 | // } 37 | 38 | setStudents(newArr); 39 | }; 40 | 41 | return ( 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {students.map((student) => { 54 | return ( 55 | 56 | 57 | 58 | 59 | 68 | 69 | ); 70 | })} 71 | 72 |
IDNameGPA
{student.id}{student.name}{student.gpa} 60 | 67 |
73 | 74 |
75 | 76 | 77 |
78 | 79 | 80 |
81 | 82 | 88 |
89 | 90 |
91 |
92 | ); 93 | } 94 | 95 | export default Students; 96 | -------------------------------------------------------------------------------- /day4/students/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/students/src/index.css -------------------------------------------------------------------------------- /day4/students/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day4/students/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tic Tac Toe 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tictactoe", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } -------------------------------------------------------------------------------- /day4/tictactoe_part1/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/tictactoe_part1/src/App.css -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import './App.css' 3 | import GameBoard from './components/GameBoard' 4 | import GameData from './data/GameData'; 5 | 6 | function App() { 7 | const [gameData, setGameData] = useState(new GameData()); 8 | 9 | return ( 10 |
11 | 12 |
13 | ) 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/components/GameBoard.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | display: inline; 3 | } 4 | 5 | button.cell { 6 | font-size: 40px; 7 | padding: 15px; 8 | } -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/components/GameBoard.jsx: -------------------------------------------------------------------------------- 1 | import GameData from "../data/GameData"; 2 | import "./GameBoard.css" 3 | import Stats from "./Stats"; 4 | 5 | function GameBoard({ game, setGame }) { 6 | 7 | const play = (cellNumber) => { 8 | game.markCell(cellNumber); 9 | game.checkWinner(); 10 | setGame(new GameData(game)); 11 | } 12 | 13 | return ( 14 |
15 |
16 | 17 |
18 | {game.cells.map((cell, index) => { 19 | return ( 20 |
21 | 22 | {((index + 1) % 3 === 0) &&
} 23 |
24 | ); 25 | })} 26 |
27 | ); 28 | } 29 | 30 | export default GameBoard; 31 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/components/Stats.jsx: -------------------------------------------------------------------------------- 1 | function Stats({ xScore, oScore, turn }) { 2 | return
3 |

Player X's Score: {xScore}

4 |

Player O's Score: {oScore}

5 |

Current Player: {turn}

6 |
7 | } 8 | 9 | export default Stats; -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/data/GameData.js: -------------------------------------------------------------------------------- 1 | import GameLogic from "./GameLogic"; 2 | 3 | class GameData { 4 | cells; 5 | turn; 6 | xScore; 7 | oScore; 8 | 9 | constructor(copiedData) { 10 | if (copiedData !== undefined && copiedData !== null) { 11 | this.cells = copiedData.cells; 12 | this.turn = copiedData.turn; 13 | this.xScore = copiedData.xScore; 14 | this.oScore = copiedData.oScore; 15 | return; 16 | } 17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 18 | this.turn = "X"; 19 | this.xScore = 0; 20 | this.oScore = 0; 21 | } 22 | 23 | resetGame() { 24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 25 | this.turn = "X"; 26 | } 27 | 28 | markCell(cellNumber) { 29 | if (this.isCellNumberInBounds(cellNumber)) 30 | return; 31 | 32 | if (this.isCellOccupied(cellNumber)) 33 | return; 34 | 35 | switch (this.turn) { 36 | case "X": 37 | this.cells[cellNumber] = "X"; 38 | break; 39 | case "O": 40 | this.cells[cellNumber] = "O"; 41 | break; 42 | } 43 | 44 | if (this.areAllCellsOccupied()) { 45 | this.resetGame(); 46 | return; 47 | } 48 | 49 | this.switchTurn(); 50 | } 51 | 52 | isCellNumberInBounds(cellNumber) { 53 | return cellNumber < 0 || cellNumber > this.cells.length-1; 54 | } 55 | 56 | isCellOccupied(cellNumber) { 57 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O" 58 | } 59 | 60 | areAllCellsOccupied() { 61 | for (let i = 0; i < this.cells.length; i++) { 62 | if (!this.isCellOccupied(i)) { 63 | return false; 64 | } 65 | } 66 | return true; 67 | } 68 | 69 | switchTurn() { 70 | switch (this.turn) { 71 | case "X": 72 | this.turn = "O"; 73 | break; 74 | case "O": 75 | this.turn = "X"; 76 | break; 77 | } 78 | } 79 | 80 | checkWinner() { 81 | let winner = GameLogic.checkWinner(this.cells); 82 | switch (winner) { 83 | case "X": 84 | this.xScore++; 85 | this.resetGame(); 86 | return "X"; 87 | case "O": 88 | this.oScore++; 89 | this.resetGame(); 90 | return "O"; 91 | } 92 | } 93 | } 94 | 95 | export default GameData; -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/data/GameLogic.js: -------------------------------------------------------------------------------- 1 | class GameLogic { 2 | static checkWinner(cells) { 3 | if (this.checkIfPlayerWon(cells, "X")) { 4 | return "X"; 5 | } else if (this.checkIfPlayerWon(cells, "O")) { 6 | return "O"; 7 | } 8 | return ""; 9 | } 10 | 11 | static checkIfPlayerWon(cells, playerType) { 12 | let firstDiagonal = 13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType; 14 | 15 | let secondDiagonal = 16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType; 17 | 18 | let firstRow = 19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType; 20 | 21 | let secondRow = 22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType; 23 | 24 | let thirdRow = 25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType; 26 | 27 | let firstColumn = 28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType; 29 | 30 | let secondColumn = 31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType; 32 | 33 | let thirdColumn = 34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType; 35 | 36 | return ( 37 | firstColumn || 38 | secondColumn || 39 | thirdColumn || 40 | firstRow || 41 | secondRow || 42 | thirdRow || 43 | firstDiagonal || 44 | secondDiagonal 45 | ); 46 | } 47 | } 48 | 49 | export default GameLogic; 50 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day4/tictactoe_part1/src/index.css -------------------------------------------------------------------------------- /day4/tictactoe_part1/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day4/tictactoe_part1/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day5/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /day5/README.md: -------------------------------------------------------------------------------- 1 | # Day 5's links: 2 | 3 | * Empty Chracter `https://emptycharacter.com/` 4 | -------------------------------------------------------------------------------- /day5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tic Tac Toe 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /day5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tictactoe", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.27", 17 | "@types/react-dom": "^18.0.10", 18 | "@vitejs/plugin-react": "^3.1.0", 19 | "vite": "^4.1.0" 20 | } 21 | } -------------------------------------------------------------------------------- /day5/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /day5/src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GDG-ASU/bc1_intro_to_web_development/0e1933a77e72f3c0149604b480e25c5dff9c62f2/day5/src/App.css -------------------------------------------------------------------------------- /day5/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import "./App.css"; 3 | import GameBoard from "./components/GameBoard"; 4 | import GameData from "./data/GameData"; 5 | 6 | function App() { 7 | const [gameData, setGameData] = useState(new GameData()); 8 | 9 | return ( 10 |
11 | 12 |
13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /day5/src/components/GameBoard.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | display: inline; 3 | } 4 | 5 | button.cell { 6 | font-size: 40px; 7 | font-weight: bold; 8 | background-color: #000000; 9 | color: #ffffff; 10 | border: 1px #ababab solid; 11 | 12 | width: 120px; 13 | height: 120px; 14 | 15 | padding: 30px; 16 | } 17 | 18 | .board { 19 | } 20 | 21 | .bigContainer { 22 | position: absolute; 23 | top: 50%; 24 | left: 50%; 25 | transform: translateX(-50%) translateY(-50%); 26 | } 27 | -------------------------------------------------------------------------------- /day5/src/components/GameBoard.jsx: -------------------------------------------------------------------------------- 1 | import GameData from "../data/GameData"; 2 | import "./GameBoard.css"; 3 | import Stats from "./Stats"; 4 | 5 | function GameBoard({ game, setGame }) { 6 | const play = (cellNumber) => { 7 | game.markCell(cellNumber); 8 | game.checkWinner(); 9 | setGame(new GameData(game)); 10 | }; 11 | 12 | return ( 13 |
14 |
15 | {game.cells.map((cell, index) => { 16 | return ( 17 |
18 | 27 | {(index + 1) % 3 === 0 &&
} 28 |
29 | ); 30 | })} 31 |
32 |
33 | 34 |
35 |
36 | ); 37 | } 38 | 39 | export default GameBoard; 40 | -------------------------------------------------------------------------------- /day5/src/components/Stats.css: -------------------------------------------------------------------------------- 1 | span { 2 | color: #ffffff; 3 | font-weight: 900; 4 | font-size: 25px; 5 | text-align: center; 6 | display: inline-block; 7 | width: 100%; 8 | } 9 | 10 | .container { 11 | display: flex; 12 | justify-content: space-between; 13 | margin-left: 20px; 14 | margin-right: 20px; 15 | margin-top: 20px; 16 | } 17 | -------------------------------------------------------------------------------- /day5/src/components/Stats.jsx: -------------------------------------------------------------------------------- 1 | import "./Stats.css"; 2 | 3 | function Stats({ xScore, oScore, turn }) { 4 | return ( 5 |
6 |
7 | Player X 8 |
9 | {xScore} 10 |
11 | 12 |
13 | Player O 14 |
15 | {oScore} 16 |
17 | 18 |
19 | Turn 20 |
21 | {turn} 22 |
23 |
24 | ); 25 | } 26 | 27 | export default Stats; 28 | -------------------------------------------------------------------------------- /day5/src/data/GameData.js: -------------------------------------------------------------------------------- 1 | import GameLogic from "./GameLogic"; 2 | 3 | class GameData { 4 | cells; 5 | turn; 6 | xScore; 7 | oScore; 8 | 9 | constructor(copiedData) { 10 | if (copiedData !== undefined && copiedData !== null) { 11 | this.cells = copiedData.cells; 12 | this.turn = copiedData.turn; 13 | this.xScore = copiedData.xScore; 14 | this.oScore = copiedData.oScore; 15 | return; 16 | } 17 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 18 | this.turn = "X"; 19 | this.xScore = 0; 20 | this.oScore = 0; 21 | } 22 | 23 | resetGame() { 24 | this.cells = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 25 | this.turn = "X"; 26 | } 27 | 28 | markCell(cellNumber) { 29 | if (this.isCellNumberInBounds(cellNumber)) 30 | return; 31 | 32 | if (this.isCellOccupied(cellNumber)) 33 | return; 34 | 35 | switch (this.turn) { 36 | case "X": 37 | this.cells[cellNumber] = "X"; 38 | break; 39 | case "O": 40 | this.cells[cellNumber] = "O"; 41 | break; 42 | } 43 | 44 | if (this.areAllCellsOccupied()) { 45 | this.resetGame(); 46 | return; 47 | } 48 | 49 | this.switchTurn(); 50 | } 51 | 52 | isCellNumberInBounds(cellNumber) { 53 | return cellNumber < 0 || cellNumber > this.cells.length-1; 54 | } 55 | 56 | isCellOccupied(cellNumber) { 57 | return this.cells[cellNumber] === "X" || this.cells[cellNumber] === "O" 58 | } 59 | 60 | areAllCellsOccupied() { 61 | for (let i = 0; i < this.cells.length; i++) { 62 | if (!this.isCellOccupied(i)) { 63 | return false; 64 | } 65 | } 66 | return true; 67 | } 68 | 69 | switchTurn() { 70 | switch (this.turn) { 71 | case "X": 72 | this.turn = "O"; 73 | break; 74 | case "O": 75 | this.turn = "X"; 76 | break; 77 | } 78 | } 79 | 80 | checkWinner() { 81 | let winner = GameLogic.checkWinner(this.cells); 82 | switch (winner) { 83 | case "X": 84 | this.xScore++; 85 | this.resetGame(); 86 | return "X"; 87 | case "O": 88 | this.oScore++; 89 | this.resetGame(); 90 | return "O"; 91 | } 92 | } 93 | } 94 | 95 | export default GameData; -------------------------------------------------------------------------------- /day5/src/data/GameLogic.js: -------------------------------------------------------------------------------- 1 | class GameLogic { 2 | static checkWinner(cells) { 3 | if (this.checkIfPlayerWon(cells, "X")) { 4 | return "X"; 5 | } else if (this.checkIfPlayerWon(cells, "O")) { 6 | return "O"; 7 | } 8 | return ""; 9 | } 10 | 11 | static checkIfPlayerWon(cells, playerType) { 12 | let firstDiagonal = 13 | cells[0] === cells[4] && cells[4] === cells[8] && cells[0] === playerType; 14 | 15 | let secondDiagonal = 16 | cells[2] === cells[4] && cells[4] === cells[6] && cells[2] === playerType; 17 | 18 | let firstRow = 19 | cells[0] === cells[1] && cells[1] === cells[2] && cells[1] === playerType; 20 | 21 | let secondRow = 22 | cells[3] === cells[4] && cells[4] === cells[5] && cells[3] === playerType; 23 | 24 | let thirdRow = 25 | cells[6] === cells[7] && cells[7] === cells[8] && cells[6] === playerType; 26 | 27 | let firstColumn = 28 | cells[0] === cells[3] && cells[3] === cells[6] && cells[0] === playerType; 29 | 30 | let secondColumn = 31 | cells[1] === cells[4] && cells[4] === cells[7] && cells[1] === playerType; 32 | 33 | let thirdColumn = 34 | cells[2] === cells[5] && cells[5] === cells[8] && cells[8] === playerType; 35 | 36 | return ( 37 | firstColumn || 38 | secondColumn || 39 | thirdColumn || 40 | firstRow || 41 | secondRow || 42 | thirdRow || 43 | firstDiagonal || 44 | secondDiagonal 45 | ); 46 | } 47 | } 48 | 49 | export default GameLogic; 50 | -------------------------------------------------------------------------------- /day5/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #000000; 3 | } 4 | -------------------------------------------------------------------------------- /day5/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /day5/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /day6/01-variables.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var x = 12 7 | var y int = x + 8 8 | fmt.Println("y = ", y) 9 | 10 | var z = 12.4 11 | fmt.Println("z + x =", (z + float64(x))) 12 | 13 | var name = "Ali" 14 | fmt.Println("Hello,", name) 15 | 16 | var firstName = "Ali" 17 | var lastName = "Al-Homsi" 18 | var fullName = fmt.Sprintf("%s %s", firstName, lastName) 19 | fmt.Println("Hello", fullName) 20 | 21 | canIDrive := false 22 | fmt.Println("Am I old enough to drive?", canIDrive) 23 | } 24 | -------------------------------------------------------------------------------- /day6/02-controlStatements.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var shallIPass = true 7 | if shallIPass { 8 | fmt.Println("You shall pass") 9 | } else { 10 | fmt.Println("You shall not pass") 11 | } 12 | 13 | x, y, z := 12, 10, 13 14 | 15 | if x > y && x > z { 16 | fmt.Println("x is the greatest") 17 | } else if y > x && y > z { 18 | fmt.Println("y is the greatest") 19 | } else if z > x && z > y { 20 | fmt.Println("z is the greatest") 21 | } 22 | 23 | var mark = "B" 24 | switch mark { 25 | case "A": 26 | fmt.Println("big nerd") 27 | case "B": 28 | fmt.Println("nerd") 29 | case "C": 30 | fmt.Println("ain't much") 31 | default: 32 | fmt.Println("I don't know what is that!") 33 | } 34 | 35 | for i := 1; i <= 10; i++ { 36 | fmt.Println(i) 37 | } 38 | 39 | var count = 10 40 | for count > 0 { 41 | fmt.Println(count) 42 | count-- 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /day6/03-arrays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var arr = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 7 | fmt.Println("all array's elements", arr) 8 | 9 | fmt.Println("element by element") 10 | // using counter loop 11 | for i := 0; i < len(arr); i++ { 12 | fmt.Println(arr[i]) 13 | } 14 | 15 | // using for range 16 | for index, value := range arr { 17 | fmt.Println(index, value) 18 | } 19 | 20 | arr[0] = 99 21 | fmt.Println(arr) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /day6/04-maps.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | mp := make(map[string]int) 7 | mp["square"] = 4 8 | mp["pentagon"] = 5 9 | mp["triangle"] = 3 10 | 11 | // using println statement 12 | fmt.Println(mp) 13 | 14 | // using for range 15 | for key, value := range mp { 16 | fmt.Println(key, value) 17 | } 18 | 19 | // delete an element 20 | delete(mp, "pentagon") 21 | fmt.Println(mp) 22 | 23 | fmt.Println("length of the map:", len(mp)) 24 | } 25 | -------------------------------------------------------------------------------- /day6/05-functions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func nothing() { 6 | fmt.Println("nothing") 7 | } 8 | 9 | func greet(name string) { 10 | fmt.Println(`Hello, ${name}`) 11 | } 12 | 13 | func square(n int) int { 14 | return n * n 15 | } 16 | 17 | var nothing2 = func() { 18 | fmt.Println("nothing2") 19 | } 20 | 21 | var add = func(x, y, z int) int { 22 | return x + y + z 23 | } 24 | 25 | func main() { 26 | nothing() 27 | greet("Jaber") 28 | fmt.Println(square(4)) 29 | nothing2() 30 | fmt.Println(add(1, 2, 3)) 31 | } 32 | -------------------------------------------------------------------------------- /day6/06-objects.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | type Student struct { 9 | Name string 10 | Phone string 11 | Gpa float64 12 | } 13 | 14 | func (s *Student) ConvertPhoneNumberToInteger() int64 { 15 | numberWithoutPlus := s.Phone[1:] 16 | 17 | intNumber, err := strconv.ParseInt(numberWithoutPlus, 10, 64) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | return intNumber 23 | } 24 | 25 | func main() { 26 | ali := Student{Name: "Ali Al-Homsi", Phone: "+962791234567", Gpa: 90.2} 27 | fmt.Println("ali's data", ali) 28 | } 29 | -------------------------------------------------------------------------------- /day6/07-errorHandling/Main.java: -------------------------------------------------------------------------------- 1 | class Main { 2 | public static double sqrt(int n) throws Exception { 3 | if (n < 0) { 4 | throw new Exception("can't take square root of a negative number"); 5 | } 6 | return Math.sqrt(n); 7 | } 8 | 9 | public static void main(String[] args) { 10 | try { 11 | double value = sqrt(-12); 12 | } catch (Exception err) { 13 | System.out.println(err.getMessage()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /day6/07-errorHandling/go.mod: -------------------------------------------------------------------------------- 1 | module x 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /day6/07-errorHandling/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | ) 8 | 9 | func sqrt(n int) (float64, error) { 10 | if n < 0 { 11 | return 0.0, errors.New("can't take square root of a negative number") 12 | } 13 | 14 | return math.Sqrt(float64(n)), nil 15 | } 16 | 17 | func main() { 18 | value, err := sqrt(-12) 19 | if err != nil { 20 | fmt.Println(err.Error()) 21 | return 22 | } 23 | 24 | fmt.Println(value) 25 | } 26 | -------------------------------------------------------------------------------- /day6/README.md: -------------------------------------------------------------------------------- 1 | # Day 6's links: 2 | 3 | * Go download: `https://go.dev/dl/` 4 | * Tour link: `https://go.dev/tour/welcome/1` 5 | * Go VSCode extension: `https://marketplace.visualstudio.com/items?itemName=golang.Go` 6 | -------------------------------------------------------------------------------- /day7/README.md: -------------------------------------------------------------------------------- 1 | # Day 7: 2 | 3 | ## Running an API's server: 4 | 5 | - Open the API's directory, e.g. `pizza-api` 6 | - Run `go run main.go` in terminal or CMD 7 | 8 | ## Importing a Postman collection: 9 | 10 | - Open Postman 11 | - Go to File -> Import -> Choose Files 12 | - Select the collection that you want to import, e.g. `Pizza API.postman_collection.json` 13 | - Click on import 14 | - Use the requests from the imported collection 15 | 16 | ## Links: 17 | 18 | - Postman: `https://www.postman.com/downloads/?utm_source=postman-home` 19 | - Open Weather API Docs `https://openweathermap.org/api` 20 | - BITCOIN API Docs `https://binance-docs.github.io/apidocs/spot/en/#exchange-information` 21 | - MariaDB: `https://mariadb.org/download` 22 | - Download Props: 23 | - Version: 10.9.5 24 | - Architecture: x86_64 25 | - OS: depends on what you use 26 | 27 | -------------------------------------------------------------------------------- /day7/queries.sql: -------------------------------------------------------------------------------- 1 | -- create tictactoe table named ttt 2 | CREATE DATABASE ttt; 3 | 4 | -- create students table 5 | CREATE TABLE IF NOT EXISTS ttt.students ( 6 | id INTEGER AUTO_INCREMENT, 7 | name VARCHAR(50), 8 | student_id VARCHAR(10), 9 | gpa FLOAT, 10 | PRIMARY KEY (id) 11 | ); 12 | 13 | -- retrive all students from database with all the attributes 14 | SELECT * FROM ttt.students; 15 | 16 | -- retrive a single student with id with all the attributes 17 | SELECT * FROM ttt.students WHERE id = 1; 18 | 19 | -- retrive a single student with id with only name and gpa attributes 20 | SELECT (name, gpa) gpa FROM ttt.students WHERE id = 1; 21 | 22 | -- update the student with id=1 and set the name to Mohammad 23 | UPDATE ttt.students SET name='Mohammad' WHERE id=1; 24 | 25 | -- delete the student with id=2 26 | DELETE FROM ttt.students WHERE id=2; 27 | 28 | -- delete all of the students 29 | DELETE FROM ttt.students; -------------------------------------------------------------------------------- /day7/rest-apis/APIs Invocations.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "2cf8da4f-e0f4-45d5-904f-0192c083243d", 4 | "name": "APIs Invocations", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "22901882" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Weather", 11 | "request": { 12 | "method": "GET", 13 | "header": [], 14 | "url": { 15 | "raw": "https://api.openweathermap.org/data/2.5/weather?q=Amman&appid=API_TOKEN_HERE", 16 | "protocol": "https", 17 | "host": [ 18 | "api", 19 | "openweathermap", 20 | "org" 21 | ], 22 | "path": [ 23 | "data", 24 | "2.5", 25 | "weather" 26 | ], 27 | "query": [ 28 | { 29 | "key": "q", 30 | "value": "Amman" 31 | }, 32 | { 33 | "key": "appid", 34 | "value": "2fb1078b1ae351cef587e0b7a1e479f0" 35 | } 36 | ] 37 | } 38 | }, 39 | "response": [] 40 | }, 41 | { 42 | "name": "BITCOIN", 43 | "request": { 44 | "method": "GET", 45 | "header": [], 46 | "url": { 47 | "raw": "https://api.binance.com/api/v3/depth?symbol=BTCUSDT", 48 | "protocol": "https", 49 | "host": [ 50 | "api", 51 | "binance", 52 | "com" 53 | ], 54 | "path": [ 55 | "api", 56 | "v3", 57 | "depth" 58 | ], 59 | "query": [ 60 | { 61 | "key": "symbol", 62 | "value": "BTCUSDT" 63 | } 64 | ] 65 | } 66 | }, 67 | "response": [] 68 | }, 69 | { 70 | "name": "BMI", 71 | "request": { 72 | "method": "GET", 73 | "header": [], 74 | "url": { 75 | "raw": "http://localhost:8080/bmi/calc?height=1.74&weight=71.5", 76 | "protocol": "http", 77 | "host": [ 78 | "localhost" 79 | ], 80 | "port": "8080", 81 | "path": [ 82 | "bmi", 83 | "calc" 84 | ], 85 | "query": [ 86 | { 87 | "key": "height", 88 | "value": "1.74" 89 | }, 90 | { 91 | "key": "weight", 92 | "value": "71.5" 93 | } 94 | ] 95 | } 96 | }, 97 | "response": [] 98 | }, 99 | { 100 | "name": "BMI List", 101 | "request": { 102 | "method": "GET", 103 | "header": [], 104 | "url": { 105 | "raw": "http://localhost:8080/bmi/all", 106 | "protocol": "http", 107 | "host": [ 108 | "localhost" 109 | ], 110 | "port": "8080", 111 | "path": [ 112 | "bmi", 113 | "all" 114 | ] 115 | } 116 | }, 117 | "response": [] 118 | } 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /day7/rest-apis/Pizza API.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "bdd346bf-6a23-431f-9821-ff4f55cf3175", 4 | "name": "Pizza API", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", 6 | "_exporter_id": "22901882" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Add", 11 | "request": { 12 | "method": "POST", 13 | "header": [], 14 | "body": { 15 | "mode": "raw", 16 | "raw": "{\n \"type\": \"Margarita\",\n \"extraCheese\": true,\n \"size\": \"medium\",\n \"price\": 3.5\n}", 17 | "options": { 18 | "raw": { 19 | "language": "json" 20 | } 21 | } 22 | }, 23 | "url": "http://localhost:8081/pizza/add" 24 | }, 25 | "response": [] 26 | }, 27 | { 28 | "name": "Get Single", 29 | "request": { 30 | "method": "GET", 31 | "header": [], 32 | "url": { 33 | "raw": "http://localhost:8081/pizza/get?type=Margarita", 34 | "protocol": "http", 35 | "host": [ 36 | "localhost" 37 | ], 38 | "port": "8081", 39 | "path": [ 40 | "pizza", 41 | "get" 42 | ], 43 | "query": [ 44 | { 45 | "key": "type", 46 | "value": "Margarita" 47 | } 48 | ] 49 | } 50 | }, 51 | "response": [] 52 | }, 53 | { 54 | "name": "Get All", 55 | "request": { 56 | "method": "GET", 57 | "header": [], 58 | "url": "http://localhost:8081/pizza/get-all" 59 | }, 60 | "response": [] 61 | }, 62 | { 63 | "name": "Update", 64 | "request": { 65 | "method": "PUT", 66 | "header": [], 67 | "body": { 68 | "mode": "raw", 69 | "raw": "{\n \"type\": \"Margarita\",\n \"extraCheese\": false,\n \"size\": \"medium\",\n \"price\": 3\n}", 70 | "options": { 71 | "raw": { 72 | "language": "json" 73 | } 74 | } 75 | }, 76 | "url": { 77 | "raw": "http://localhost:8081/pizza/update?type=Margarita", 78 | "protocol": "http", 79 | "host": [ 80 | "localhost" 81 | ], 82 | "port": "8081", 83 | "path": [ 84 | "pizza", 85 | "update" 86 | ], 87 | "query": [ 88 | { 89 | "key": "type", 90 | "value": "Margarita" 91 | } 92 | ] 93 | } 94 | }, 95 | "response": [] 96 | }, 97 | { 98 | "name": "Delete", 99 | "request": { 100 | "method": "DELETE", 101 | "header": [], 102 | "url": { 103 | "raw": "http://localhost:8081/pizza/delete?type=Margarita", 104 | "protocol": "http", 105 | "host": [ 106 | "localhost" 107 | ], 108 | "port": "8081", 109 | "path": [ 110 | "pizza", 111 | "delete" 112 | ], 113 | "query": [ 114 | { 115 | "key": "type", 116 | "value": "Margarita" 117 | } 118 | ] 119 | } 120 | }, 121 | "response": [] 122 | } 123 | ] 124 | } -------------------------------------------------------------------------------- /day7/rest-apis/bmi-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | ) 8 | 9 | func calculateBMI(height, wight float64) (string, float64) { 10 | bmi := wight / (height * height) 11 | var index string 12 | 13 | if bmi < 18.5 { 14 | index = "Underweight" 15 | } else if bmi >= 18.5 && bmi <= 24.9 { 16 | index = "Normal Weight" 17 | } else if bmi >= 25 && bmi <= 29.9 { 18 | index = "Overweight" 19 | } else { 20 | index = "Obesity" 21 | } 22 | 23 | return index, bmi 24 | } 25 | 26 | // handleCalculateBMI calculates Body Mass Index, and returns a response 27 | // with BMI, and status based on that index. 28 | func handleCalculateBMI(res http.ResponseWriter, req *http.Request) { 29 | height, _ := strconv.ParseFloat(req.URL.Query().Get("height"), 64) 30 | weight, _ := strconv.ParseFloat(req.URL.Query().Get("weight"), 64) 31 | 32 | index, bmi := calculateBMI(height, weight) 33 | 34 | res.Header().Set("Content-Type", "application/json") 35 | res.Header().Set("Access-Control-Allow-Origin", "*") 36 | json.NewEncoder(res).Encode(map[string]any{ 37 | "index": index, 38 | "bmi": bmi, 39 | }) 40 | } 41 | 42 | // handleListBMI returns a response with a map with BMI categories 43 | func handleListBMI(res http.ResponseWriter, req *http.Request) { 44 | res.Header().Set("Content-Type", "application/json") 45 | res.Header().Set("Access-Control-Allow-Origin", "*") 46 | json.NewEncoder(res).Encode(map[string]string{ 47 | "Underweight": "< 18.5", 48 | "Normal weight": "18.5 - 24.9", 49 | "Overweight": "25 - 29.9", 50 | "Obesity": "> 30", 51 | }) 52 | } 53 | 54 | func main() { 55 | router := http.NewServeMux() 56 | router.HandleFunc("/bmi/calc", handleCalculateBMI) 57 | router.HandleFunc("/bmi/all", handleListBMI) 58 | 59 | http.ListenAndServe(":8080", router) 60 | } 61 | -------------------------------------------------------------------------------- /day7/rest-apis/pizza-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | var pizzas = make(map[string]Pizza) 9 | 10 | type Pizza struct { 11 | Type string `json:"type"` 12 | ExtraCheese bool `json:"extraCheese"` 13 | Size string `json:"size"` 14 | Price float64 `json:"price"` 15 | } 16 | 17 | func handleGetPizza(res http.ResponseWriter, req *http.Request) { 18 | _type := req.URL.Query().Get("type") 19 | pizza, ok := pizzas[_type] 20 | if ok { 21 | json.NewEncoder(res).Encode(pizza) 22 | return 23 | } 24 | 25 | // res.WriteHeader(404) 26 | res.WriteHeader(http.StatusNotFound) 27 | } 28 | 29 | func handleGetPizzas(res http.ResponseWriter, req *http.Request) { 30 | pizzasArr := make([]Pizza, 0) 31 | 32 | for _, pizza := range pizzas { 33 | pizzasArr = append(pizzasArr, pizza) 34 | } 35 | 36 | json.NewEncoder(res).Encode(pizzasArr) 37 | } 38 | 39 | func handleAddPizza(res http.ResponseWriter, req *http.Request) { 40 | var pizza Pizza 41 | err := json.NewDecoder(req.Body).Decode(&pizza) 42 | if err != nil { 43 | res.WriteHeader(http.StatusBadRequest) 44 | return 45 | } 46 | 47 | pizzas[pizza.Type] = pizza 48 | } 49 | 50 | func handleUpdatePizza(res http.ResponseWriter, req *http.Request) { 51 | _type := req.URL.Query().Get("type") 52 | 53 | _, exists := pizzas[_type] 54 | if len(_type) == 0 || !exists { 55 | res.WriteHeader(http.StatusBadRequest) 56 | return 57 | } 58 | 59 | var pizza Pizza 60 | err := json.NewDecoder(req.Body).Decode(&pizza) 61 | if err != nil { 62 | res.WriteHeader(http.StatusBadRequest) 63 | return 64 | } 65 | 66 | pizzas[_type] = pizza 67 | } 68 | 69 | func handleDeletePizza(res http.ResponseWriter, req *http.Request) { 70 | _type := req.URL.Query().Get("type") 71 | 72 | _, exists := pizzas[_type] 73 | if len(_type) == 0 || !exists { 74 | res.WriteHeader(http.StatusBadRequest) 75 | return 76 | } 77 | 78 | delete(pizzas, _type) 79 | } 80 | 81 | func main() { 82 | router := http.NewServeMux() 83 | router.HandleFunc("/pizza/get", handleGetPizza) 84 | router.HandleFunc("/pizza/get-all", handleGetPizzas) 85 | router.HandleFunc("/pizza/add", handleAddPizza) 86 | router.HandleFunc("/pizza/update", handleUpdatePizza) 87 | router.HandleFunc("/pizza/delete", handleDeletePizza) 88 | 89 | http.ListenAndServe(":8081", router) 90 | } 91 | -------------------------------------------------------------------------------- /day8/README.md: -------------------------------------------------------------------------------- 1 | # Day 8: 2 | 3 | - Install `mariadb` database driver for Go: 4 | - Open terminal or CMD inside a project 5 | - Run `go get -u github.com/go-sql-driver/mysql` 6 | - Make a databse connection like in the file `db/db.go` 7 | 8 | - Using MariaDB CMD client (Windows) 9 | - Open start 10 | - Search for `MariaDB` 11 | - You'll find a program called `MySQL Client (MariaDB 10.9 (x64))` 12 | - Open it, enter your database password 13 | - Run your SQL queries 14 | 15 | - Using MariaDB CMD client (Linux & MacOS) 16 | - Open terminal 17 | - Run `mariadb -u root -p` 18 | - Enter your database password 19 | - Run your SQL queries 20 | 21 | -------------------------------------------------------------------------------- /day8/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var connectionInstance *sql.DB = nil 10 | 11 | func GetInstance() *sql.DB { 12 | if connectionInstance == nil { 13 | var err error 14 | // DB_USERNAME is root by default 15 | // DB_PASSWORD is what you've set when installing the database 16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName` 17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True") 18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True") 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | return connectionInstance 24 | } 25 | -------------------------------------------------------------------------------- /day8/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ttt; 2 | 3 | CREATE TABLE IF NOT EXISTS ttt.players ( 4 | id INTEGER AUTO_INCREMENT, 5 | username VARCHAR(60) UNIQUE NOT NULL, 6 | password VARCHAR(200), 7 | created_at TIMESTAMP, 8 | updated_at TIMESTAMP, 9 | PRIMARY KEY (id) 10 | ); -------------------------------------------------------------------------------- /day8/db/player_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type PlayerDB struct{} 6 | 7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)", 10 | player.Username, player.Password) 11 | if err != nil { 12 | return models.Player{}, err 13 | } 14 | 15 | playerId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Player{}, err 18 | } 19 | 20 | player.Id = int(playerId) 21 | 22 | return player, nil 23 | } 24 | 25 | func (p *PlayerDB) Get(username string) (models.Player, error) { 26 | var player models.Player 27 | err := GetInstance(). 28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username). 29 | Scan(&player.Id, &player.Username, &player.Password) 30 | 31 | if err != nil { 32 | return models.Player{}, err 33 | } 34 | 35 | return player, nil 36 | } 37 | 38 | func (p *PlayerDB) Update(username string, player models.Player) error { 39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (p *PlayerDB) Delete(username string) error { 47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } -------------------------------------------------------------------------------- /day8/go.mod: -------------------------------------------------------------------------------- 1 | module tictactoe 2 | 3 | go 1.20 4 | 5 | require github.com/go-sql-driver/mysql v1.7.0 // indirect 6 | -------------------------------------------------------------------------------- /day8/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | -------------------------------------------------------------------------------- /day8/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "tictactoe/db" 5 | ) 6 | 7 | func main() { 8 | playerDB := new(db.PlayerDB) 9 | err := playerDB.Delete("Laith") 10 | if err != nil { 11 | panic(err) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /day8/models/player.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Player struct { 6 | Id int 7 | Username string 8 | Password string 9 | CreatedAt time.Time 10 | UpdatedAt time.Time 11 | } 12 | -------------------------------------------------------------------------------- /day9/README.md: -------------------------------------------------------------------------------- 1 | # Day 9: 2 | 3 | - Install `bcrypt` hash for Go: 4 | - Open terminal or CMD inside a project 5 | - Run `go get -u golang.org/x/crypto/bcrypt` 6 | - Check `controllers/login_api.go` for more details about the hash 7 | -------------------------------------------------------------------------------- /day9/controllers/login_api.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "tictactoe/db" 7 | "tictactoe/models" 8 | 9 | "golang.org/x/crypto/bcrypt" 10 | ) 11 | 12 | type LoginAPI struct { 13 | playerDB *db.PlayerDB 14 | } 15 | 16 | func NewLoginAPI(playerDB *db.PlayerDB) *LoginAPI { 17 | return &LoginAPI{ 18 | playerDB: playerDB, 19 | } 20 | } 21 | 22 | type reqBody struct { 23 | Username string `json:"username"` 24 | Password string `json:"password"` 25 | } 26 | 27 | func (l *LoginAPI) HandleSignup(resp http.ResponseWriter, req *http.Request) { 28 | var requestBody reqBody 29 | 30 | err := json.NewDecoder(req.Body).Decode(&requestBody) 31 | if err != nil { 32 | resp.WriteHeader(http.StatusBadRequest) 33 | return 34 | } 35 | 36 | hashed, err := bcrypt.GenerateFromPassword([]byte(requestBody.Password), bcrypt.MinCost) 37 | if err != nil { 38 | resp.WriteHeader(http.StatusInternalServerError) 39 | return 40 | } 41 | 42 | requestBody.Password = string(hashed) 43 | 44 | player, err := l.playerDB.Create(models.Player{ 45 | Username: requestBody.Username, 46 | Password: requestBody.Password, 47 | }) 48 | if err != nil { 49 | resp.WriteHeader(http.StatusInternalServerError) 50 | return 51 | } 52 | 53 | resp.Header().Set("Content-Type", "application/json") 54 | json.NewEncoder(resp).Encode(player) 55 | } 56 | 57 | func (l *LoginAPI) HandleLogin(resp http.ResponseWriter, req *http.Request) { 58 | 59 | } 60 | -------------------------------------------------------------------------------- /day9/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | ) 8 | 9 | var connectionInstance *sql.DB = nil 10 | 11 | func GetInstance() *sql.DB { 12 | if connectionInstance == nil { 13 | var err error 14 | // DB_USERNAME is root by default 15 | // DB_PASSWORD is what you've set when installing the database 16 | // DB_NAME is the database you've created using `CREATE DATABASE whateverName` 17 | // connectionInstance, err = sql.Open("mysql", "DB_USERNAME:DB_PASSWORD@tcp(localhost:3306)/DB_NAME?parseTime=True") 18 | connectionInstance, err = sql.Open("mysql", "root:1234@tcp(localhost:3306)/ttt?parseTime=True") 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | return connectionInstance 24 | } 25 | -------------------------------------------------------------------------------- /day9/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE ttt; 2 | 3 | CREATE TABLE IF NOT EXISTS ttt.players ( 4 | id INTEGER AUTO_INCREMENT, 5 | username VARCHAR(60) UNIQUE NOT NULL, 6 | password VARCHAR(200), 7 | created_at TIMESTAMP, 8 | updated_at TIMESTAMP, 9 | PRIMARY KEY (id) 10 | ); 11 | 12 | CREATE TABLE IF NOT EXISTS ttt.sessions ( 13 | id INTEGER AUTO_INCREMENT, 14 | username VARCHAR(60), 15 | token VARCHAR(32), 16 | PRIMARY KEY (id) 17 | ); -------------------------------------------------------------------------------- /day9/db/player_db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import "tictactoe/models" 4 | 5 | type PlayerDB struct{} 6 | 7 | func (p *PlayerDB) Create(player models.Player) (models.Player, error) { 8 | result, err := GetInstance().Exec( 9 | "INSERT INTO ttt.players (username, password, created_at) VALUES (?, ?, current_timestamp)", 10 | player.Username, player.Password) 11 | if err != nil { 12 | return models.Player{}, err 13 | } 14 | 15 | playerId, err := result.LastInsertId() 16 | if err != nil { 17 | return models.Player{}, err 18 | } 19 | 20 | player.Id = int(playerId) 21 | 22 | return player, nil 23 | } 24 | 25 | func (p *PlayerDB) Get(username string) (models.Player, error) { 26 | var player models.Player 27 | err := GetInstance(). 28 | QueryRow("SELECT id, username, password FROM ttt.players WHERE username=?", username). 29 | Scan(&player.Id, &player.Username, &player.Password) 30 | 31 | if err != nil { 32 | return models.Player{}, err 33 | } 34 | 35 | return player, nil 36 | } 37 | 38 | func (p *PlayerDB) Update(username string, player models.Player) error { 39 | _, err := GetInstance().Exec("UPDATE ttt.players SET username=?, password=? WHERE username=?", player.Username, player.Password, username) 40 | if err != nil { 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func (p *PlayerDB) Delete(username string) error { 47 | _, err := GetInstance().Exec("DELETE FROM ttt.players WHERE username=?", username) 48 | if err != nil { 49 | return err 50 | } 51 | return nil 52 | } -------------------------------------------------------------------------------- /day9/go.mod: -------------------------------------------------------------------------------- 1 | module tictactoe 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-sql-driver/mysql v1.7.0 // indirect 7 | golang.org/x/crypto v0.6.0 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /day9/go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 2 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 3 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 4 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 5 | -------------------------------------------------------------------------------- /day9/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "tictactoe/router" 5 | ) 6 | 7 | func main() { 8 | router.StartServer() 9 | } 10 | -------------------------------------------------------------------------------- /day9/models/player.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type Player struct { 6 | Id int `json:"id"` 7 | Username string `json:"username"` 8 | Password string `json:"password"` 9 | CreatedAt time.Time `json:"-"` 10 | UpdatedAt time.Time `json:"-"` 11 | } 12 | -------------------------------------------------------------------------------- /day9/models/session.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Session struct { 4 | Username string `json:"-"` 5 | Token string `json:"-"` 6 | } -------------------------------------------------------------------------------- /day9/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "tictactoe/controllers" 6 | "tictactoe/db" 7 | ) 8 | 9 | func StartServer() { 10 | playerDB := new(db.PlayerDB) 11 | 12 | loginAPI := controllers.NewLoginAPI(playerDB) 13 | 14 | router := http.NewServeMux() 15 | router.HandleFunc("/player/login", loginAPI.HandleLogin) 16 | router.HandleFunc("/player/signup", loginAPI.HandleSignup) 17 | 18 | http.ListenAndServe(":8080", router) 19 | } 20 | --------------------------------------------------------------------------------