massive: move a lot of things

This commit is contained in:
Inanc Gumus
2019-04-26 21:32:20 +03:00
parent da30b109f8
commit 0547b1e320
105 changed files with 2624 additions and 192 deletions

View File

@@ -1 +0,0 @@
This section is in progress. I'm working hard to update the course all the time. Hold on!

View File

@@ -1,24 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
)
func main() {
in := bufio.NewScanner(os.Stdin)
p, report := new(parser), newReport()
for in.Scan() {
report.update(p.parse(in.Text()))
}
summarize(report, p.err(), in.Err())
}

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org 4
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org -100
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org FOUR
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,29 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"os"
"github.com/inancgumus/learngo/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics"
)
func main() {
in := bufio.NewScanner(os.Stdin)
parser, report := metrics.NewParser(), metrics.NewReport()
for in.Scan() {
report.Update(parser.Parse(in.Text()))
}
summarize(report, parser.Err(), in.Err())
// s, _ := json.Marshal(report)
// fmt.Println(string(s))
}

View File

@@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package metrics
import (
"fmt"
)
// Parser keep tracks of the parsing
type Parser struct {
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// Parsed wraps a result for generating parser error
type Parsed struct {
result // use struct embedding
err error // inject an error
}
// NewParser returns a new parser
func NewParser() *Parser {
return new(Parser)
}
// Parse parses a log line and returns a result with an injected error
func (p *Parser) Parse(line string) (parsed Parsed) {
// always set the error
defer func() { parsed.err = p.lerr }()
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
res, err := parse(line)
if p.lines++; err != nil {
p.lerr = fmt.Errorf("%s: (line #%d)", err, p.lines)
}
return Parsed{result: res}
}
// Err returns the last error encountered
func (p *Parser) Err() error {
return p.lerr
}

View File

@@ -1,89 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package metrics
import (
"encoding/json"
"sort"
)
// Report aggregates the final report
type Report struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
total result // total visits for all domains
}
// NewReport constructs and initializes a new report
func NewReport() *Report {
return &Report{sum: make(map[string]result)}
}
// Update updates the report for the given parsing result
func (r *Report) Update(parsed Parsed) {
// do not update the report if the result has an error
if parsed.err != nil {
return
}
domain := parsed.Domain
if _, ok := r.sum[domain]; !ok {
r.domains = append(r.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
r.total = r.total.add(parsed.result)
r.sum[domain] = parsed.add(r.sum[domain])
}
// Iterator returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func (r *Report) Iterator() (next func() bool, cur func() result) {
sort.Strings(r.domains)
// remember the last iterated result
var last int
next = func() bool {
defer func() { last++ }()
return len(r.domains) > last
}
cur = func() result {
// returns a copy so the caller cannot change it
name := r.domains[last-1]
return r.sum[name]
}
return
}
// Total returns the total metrics
func (r *Report) Total() Parsed {
return Parsed{result: r.total}
}
// MarshalJSON marshals a report to JSON
// Alternative: unexported embedding
func (r *Report) MarshalJSON() ([]byte, error) {
type total struct {
*result
IgnoreDomain *string `json:"Domain,omitempty"`
}
return json.Marshal(struct {
Sum map[string]result
Domains []string
Total total
}{
Sum: r.sum, Domains: r.domains, Total: total{result: &r.total},
})
}

View File

@@ -1,51 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package metrics
import (
"fmt"
"strconv"
"strings"
)
// always put all the related things together as in here
// result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type result struct {
Domain string
Visits int
// add more metrics if needed
}
// add adds the metrics of another Result to itself and returns a new Result
func (r result) add(other result) result {
return result{
Domain: r.Domain,
Visits: r.Visits + other.Visits,
}
}
// parse parses a single log line
func parse(line string) (parsed result, err error) {
fields := strings.Fields(line)
if len(fields) != 2 {
err = fmt.Errorf("wrong input: %v", fields)
return
}
parsed.Domain = fields[0]
parsed.Visits, err = strconv.Atoi(fields[1])
if parsed.Visits < 0 || err != nil {
err = fmt.Errorf("wrong input: %q", fields[1])
}
return
}

View File

@@ -1,40 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
"github.com/inancgumus/learngo/x-tba/2-methods/xxx-log-parser-methods/packaged/metrics"
)
// summarize prints the report and errors if any
func summarize(rep *metrics.Report, errs ...error) {
fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS")
fmt.Println(strings.Repeat("-", 45))
next, cur := rep.Iterator()
for next() {
rec := cur()
fmt.Printf("%-30s %10d\n", rec.Domain, rec.Visits)
}
fmt.Printf("\n%-30s %10d\n", "TOTAL", rep.Total().Visits)
// only handle the errors once
dumpErrs(errs...)
}
// this variadic func simplifies the multiple error handling
func dumpErrs(errs ...error) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
}
}
}

View File

@@ -1,40 +0,0 @@
package main
import (
"fmt"
)
// parser keep tracks of the parsing
type parser struct {
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// parsed wraps a result for generating parser error
type parsed struct {
result // use struct embedding
err error // inject an error
}
// parse parses a log line and returns a result with an injected error
func (p *parser) parse(line string) (pv parsed) {
// always set the error
defer func() { pv.err = p.lerr }()
// if there was an error do not continue
if p.lerr != nil {
return
}
// chain the parser's error to the result's
res, err := parseLine(line)
if p.lines++; err != nil {
p.lerr = fmt.Errorf("%s: (line #%d)", err, p.lines)
}
return parsed{result: res}
}
// err returns the last error encountered
func (p *parser) err() error {
return p.lerr
}

View File

@@ -1,64 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "sort"
// report aggregates the final report
type report struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
total result // total visits for all domains
}
// newReport constructs and initializes a new report
func newReport() *report {
return &report{sum: make(map[string]result)}
}
// update updates the errors for the given parsing result
func (r *report) update(parsed parsed) {
// do not update the report if the result has an error
if parsed.err != nil {
return
}
domain := parsed.domain
if _, ok := r.sum[domain]; !ok {
r.domains = append(r.domains, domain)
}
// let the result handle the addition
// this allows us to manage the result in once place
// and this way it becomes easily extendable
r.total = r.total.add(parsed.result)
r.sum[domain] = parsed.add(r.sum[domain])
}
// iterator returns `next()` to detect when the iteration ends,
// and a `cur()` to return the current result.
// iterator iterates sorted by domains.
func (r *report) iterator() (next func() bool, cur func() result) {
sort.Strings(r.domains)
// remember the last iterated result
var last int
next = func() bool {
defer func() { last++ }()
return len(r.domains) > last
}
cur = func() result {
// returns a copy so the caller cannot change it
name := r.domains[last-1]
return r.sum[name]
}
return
}

View File

@@ -1,51 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
// always put all the related things together as in here
// result stores metrics for a domain
// it uses the value mechanics,
// because it doesn't have to update anything
type result struct {
domain string
visits int
// add more metrics if needed
}
// add adds the metrics of another result to itself and returns a new result
func (r result) add(other result) result {
return result{
domain: r.domain,
visits: r.visits + other.visits,
}
}
// parseLine parses a single result line
func parseLine(line string) (parsed result, err error) {
fields := strings.Fields(line)
if len(fields) != 2 {
err = fmt.Errorf("wrong input: %v", fields)
return
}
parsed.domain = fields[0]
parsed.visits, err = strconv.Atoi(fields[1])
if parsed.visits < 0 || err != nil {
err = fmt.Errorf("wrong input: %q", fields[1])
}
return
}

View File

@@ -1,38 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
)
// summarize prints the report and errors if any
func summarize(rep *report, errs ...error) {
fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS")
fmt.Println(strings.Repeat("-", 45))
next, cur := rep.iterator()
for next() {
rec := cur()
fmt.Printf("%-30s %10d\n", rec.domain, rec.visits)
}
fmt.Printf("\n%-30s %10d\n", "TOTAL", rep.total.visits)
// only handle the errors once
dumpErrs(errs...)
}
// this variadic func simplifies the multiple error handling
func dumpErrs(errs ...error) {
for _, err := range errs {
if err != nil {
fmt.Printf("> Err: %s\n", err)
}
}
}

View File

@@ -1 +0,0 @@
This section is in progress. I'm working hard to update the course all the time. Hold on!

View File

@@ -1 +0,0 @@
This section is in progress. I'm working hard to update the course all the time. Hold on!

View File

@@ -0,0 +1,38 @@
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
)
// Film represents a single Star Wars film
type Film struct {
// All fields should be exported to be decoded
Title string `json:"title"`
Opening string `json:"opening_crawl"`
Starships []string `json:"starships"`
}
func (f Film) String() string {
var buf strings.Builder
fmt.Fprintln(&buf, f.Title)
fmt.Fprintln(&buf, strings.Repeat("-", len(f.Title)))
buf.WriteByte('\n')
fmt.Fprintln(&buf, f.Opening)
return buf.String()
}
// fetchFilm returns a Film resource by its id
func fetchFilm(ctx context.Context, id int) (film Film, err error) {
fmt.Println("requesting", fmt.Sprintf(swapi+"films/%d/", id))
c, err := request(ctx, fmt.Sprintf(swapi+"films/%d/", id))
if err != nil {
return film, err
}
return film, json.Unmarshal(c, &film)
}

View File

@@ -0,0 +1,84 @@
package main
import (
"context"
"fmt"
"os"
"strconv"
"time"
)
// const
// nil, string, int, float64, bool, comparison
// variables
// multiple short variables
// assignment?
// if
// error handling
// functions
// returns
// defer
// struct
// encoding/json
// pointers
// concurrency
// select
// chan receive
// fmt
// Printf
// Sprintf
// Errorf
// net/http
// Get
// context/Context
// TODO: convert fmt calls to log
// TODO: you can make the fetcher a library and main package the user
// TODO: you can generate an html for the ship details? template pkg.
const timeout = 10 * time.Second
func main() {
args := os.Args[1:]
quit("give me a film id", len(args) != 1)
id, err := strconv.Atoi(args[0])
quit("film id is incorrrect", err)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// TODO: print the ship details to a text file (or any io.Writer)
film, err := fetchFilm(ctx, id)
quit("Error occurred while fetching the film data", err)
fmt.Println(film)
// a channel also can be used to print as they come
ships := make([]Starship, len(film.Starships))
err = fetchStarships(ctx, film.Starships, ships)
quit("Error occurred while fetching starships", err)
fmt.Println("Ships used in the movie:")
fmt.Println("------------------------")
for _, ship := range ships {
fmt.Println(ship)
}
}
func quit(message string, cond interface{}) {
var quit bool
switch v := cond.(type) {
case error:
quit = true
message += ": " + v.Error()
case bool:
quit = v
}
if quit {
fmt.Fprintln(os.Stderr, message)
os.Exit(1)
}
}

View File

@@ -0,0 +1,44 @@
package main
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
)
// MaxResponseSize limits the response bytes from the API
const MaxResponseSize = 2 << 16
// creating a robust http getter (lecture? :))
func request(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
resp, err := http.DefaultClient.Do(req)
if err != nil {
// Get the error from the context.
// It may contain more useful data.
select {
case <-ctx.Done():
err = ctx.Err()
default:
}
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Bad Status: %d", resp.StatusCode)
}
// Prevents the api to shoot us unlimited amount of data
r := io.LimitReader(resp.Body, MaxResponseSize)
return ioutil.ReadAll(r)
}

View File

@@ -0,0 +1,69 @@
package main
import (
"context"
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
)
// Starship represents a Star Wars space ship
type Starship struct {
// All fields should be exported to be decoded
Name string `json:"name"`
Credits json.Number `json:"cost_in_credits"`
Rating float64 `json:"hyperdrive_rating,string"`
}
func (ship Starship) String() string {
var buf strings.Builder
fmt.Fprintf(&buf, "Ship Name: %s\n", ship.Name)
fmt.Fprintf(&buf, "Price : %s\n", ship.Credits)
return buf.String()
}
// TODO: return error instead of quit()
func fetchStarships(ctx context.Context, shipUrls []string, ships []Starship) error {
ships = ships[:0]
re := regexp.MustCompile(`/([0-9]+)/$`)
for _, url := range shipUrls {
ids := re.FindAllStringSubmatch(url, -1)
if ids == nil {
continue
}
sid := ids[0][1]
id, _ := strconv.Atoi(sid)
// TODO: goroutine
ship, err := fetchStarship(ctx, id)
if err != nil {
return err
}
ships = append(ships, ship)
}
return nil
}
// fetchStarship returns Starship info by its id
func fetchStarship(ctx context.Context, id int) (ship Starship, err error) {
c, err := request(ctx, fmt.Sprintf(swapi+"starships/%d/", id))
if err != nil {
return ship, err
}
// -> If your response body is small enough,
// just read it all into memory using ioutil.ReadAll
// and use json.Unmarshal.
//
// -> Do not use json.Decoder if you are not dealing
// with JSON streaming.
return ship, json.Unmarshal(c, &ship)
}

View File

@@ -0,0 +1,3 @@
package main
const swapi = "https://swapi.co/api/"

View File

@@ -0,0 +1,141 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
const (
// skin options :-)
emptyMark = "☆"
mark1 = "💀"
mark2 = "🎈"
banner = `
~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~`
maxTurn = 9
prompt = "\n%s [1-9]: "
)
func main() {
// -------------------------------------------------
// INITIALIZE THE GAME
// -------------------------------------------------
fmt.Println(banner)
in := bufio.NewScanner(os.Stdin)
var (
turn int
player = mark1
board = [3][3]string{
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
}
)
game:
for {
// -------------------------------------------------
// PRINT THE BOARD AND THE PROMPT
// -------------------------------------------------
fmt.Println()
fmt.Println("---+----+---")
for _, line := range board {
fmt.Printf("%s\n", strings.Join(line[:], " | "))
fmt.Println("---+----+---")
}
fmt.Printf(prompt, player)
// get the input
if !in.Scan() {
break
}
// -------------------------------------------------
// CHECK THE MOVE AND PLAY
// -------------------------------------------------
var (
pos int
row, col int
)
// Atoi already return 0 on error; no need to check
// it for the following switch to work
pos, _ = strconv.Atoi(in.Text())
switch {
case pos >= 7 && pos <= 9:
row = 2
case pos >= 4 && pos <= 6:
row = 1
case pos >= 1 && pos <= 3:
row = 0
default:
fmt.Println("\n>>>", "wrong position!")
continue
}
col = pos - row*3 - 1
if board[row][col] != emptyMark {
fmt.Println("\n>>>", "already played!")
continue
}
// put a mark on the board
board[row][col] = player
// -------------------------------------------------
// IS THERE A WINNER? OR IS IT A TIE?
// -------------------------------------------------
var won bool
for _, m := range [2]string{mark1, mark2} {
b, mmm := board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0][:], "") == mmm ||
strings.Join(b[1][:], "") == mmm ||
strings.Join(b[2][:], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
break
}
}
if won {
fmt.Printf("\nWINNER: %s\n", player)
break game
} else if turn++; turn == maxTurn {
fmt.Printf("\nTIE!\n")
break game
}
// -------------------------------------------------
// CHANGE THE MARKER TO THE NEXT PLAYER
// -------------------------------------------------
if player == mark1 {
player = mark2
} else {
player = mark1
}
}
}

View File

@@ -0,0 +1,156 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
const (
// skin options :-)
emptyMark = "☆"
mark1 = "💀"
mark2 = "🎈"
banner = `
~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~`
maxTurn = 9
)
func main() {
fmt.Println(banner)
loop(bufio.NewScanner(os.Stdin))
}
func loop(in *bufio.Scanner) {
var (
turn int
player = mark1
board = createBoard()
)
for {
prompt(board, player)
if !in.Scan() {
break
}
msg := play(board, player, getMove(in.Text()))
if msg != "" {
fmt.Println("\n>>>", msg)
continue
}
turn++
if msg := finito(board, turn, player); msg != "" {
printBoard(board, player)
fmt.Printf("\n%s\n", msg)
break
}
player = switchTo(player)
}
}
func createBoard() [][]string {
return [][]string{
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
}
}
func prompt(board [][]string, player string) {
printBoard(board, player)
fmt.Printf("\n%s [1-9]: ", player)
}
func printBoard(board [][]string, player string) {
fmt.Println()
fmt.Println("---+----+---")
for _, line := range board {
fmt.Printf("%s\n", strings.Join(line, " | "))
fmt.Println("---+----+---")
}
}
func getMove(move string) int {
// Atoi already return 0 on error; no need to check
// it for the following switch to work
pos, _ := strconv.Atoi(move)
return pos
}
func play(board [][]string, player string, pos int) string {
var row int
switch {
case pos >= 7 && pos <= 9:
row = 2
case pos >= 4 && pos <= 6:
row = 1
case pos >= 1 && pos <= 3:
row = 0
default:
return "wrong position"
}
col := pos - row*3 - 1
if board[row][col] != emptyMark {
return "already played!"
}
// put the player
board[row][col] = player
return ""
}
func finito(board [][]string, turn int, player string) string {
switch {
case won(board):
return fmt.Sprintf("WINNER: %s", player)
case turn == maxTurn:
return "TIE!"
}
return ""
}
func won(board [][]string) (won bool) {
for _, m := range [2]string{mark1, mark2} {
b, mmm := board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
func switchTo(player string) string {
if player == mark1 {
return mark2
}
return mark1
}

View File

@@ -0,0 +1,162 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
const (
// skin options :-)
emptyMark = "☆"
mark1 = "💀"
mark2 = "🎈"
banner = `
~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~`
maxTurn = 9
)
type game struct {
board [][]string
turn int
player string
}
func main() {
fmt.Println(banner)
loop(bufio.NewScanner(os.Stdin))
}
func loop(in *bufio.Scanner) {
g := game{
player: mark1,
board: createBoard(),
}
for {
prompt(g)
if !in.Scan() {
break
}
msg := play(g, getMove(in.Text()))
if msg != "" {
fmt.Println("\n>>>", msg)
continue
}
g.turn++
if msg := finito(g); msg != "" {
printBoard(g.board)
fmt.Printf("\n%s\n", msg)
break
}
g.player = switchTo(g.player)
}
}
func createBoard() [][]string {
return [][]string{
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
}
}
func prompt(g game) {
printBoard(g.board)
fmt.Printf("\n%s [1-9]: ", g.player)
}
func printBoard(board [][]string) {
fmt.Println()
fmt.Println("---+----+---")
for _, line := range board {
fmt.Printf("%s\n", strings.Join(line, " | "))
fmt.Println("---+----+---")
}
}
func getMove(move string) int {
// Atoi already return 0 on error; no need to check
// it for the following switch to work
pos, _ := strconv.Atoi(move)
return pos
}
// NOTE: manipulates the game object
func play(g game, pos int) string {
var row int
switch {
case pos >= 7 && pos <= 9:
row = 2
case pos >= 4 && pos <= 6:
row = 1
case pos >= 1 && pos <= 3:
row = 0
default:
return "wrong position"
}
col := pos - row*3 - 1
if g.board[row][col] != emptyMark {
return "already played!"
}
// put the player
g.board[row][col] = g.player
return ""
}
func finito(g game) string {
switch {
case won(g.board):
return fmt.Sprintf("WINNER: %s", g.player)
case g.turn == maxTurn:
return "TIE!"
}
return ""
}
func won(board [][]string) (won bool) {
for _, m := range [2]string{mark1, mark2} {
b, mmm := board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
func switchTo(player string) string {
if player == mark1 {
return mark2
}
return mark1
}

View File

@@ -0,0 +1,158 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
const (
// skin options :-)
emptyMark = "☆"
mark1 = "💀"
mark2 = "🎈"
banner = `
~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~`
maxTurn = 9
)
func main() {
fmt.Println(banner)
loop(bufio.NewScanner(os.Stdin))
}
type game struct {
board [][]string
turn int
player string
}
func loop(in *bufio.Scanner) {
g := newGame()
for {
g.prompt()
if !in.Scan() {
break
}
// Atoi already return 0 on error; no need to check
// it for the following switch to work
pos, _ := strconv.Atoi(in.Text())
msg := g.play(pos)
if msg != "" {
fmt.Println("\n>>>", msg)
continue
}
g.turn++
if msg := g.finito(); msg != "" {
g.print()
fmt.Printf("\n%s\n", msg)
break
}
g.player = switchTo(g.player)
}
}
func newGame() game {
return game{
player: mark1,
board: [][]string{
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
},
}
}
func (g game) prompt() {
g.print()
fmt.Printf("\n%s [1-9]: ", g.player)
}
func (g game) print() {
fmt.Println()
fmt.Println("---+----+---")
for _, line := range g.board {
fmt.Printf("%s\n", strings.Join(line, " | "))
fmt.Println("---+----+---")
}
}
func (g game) play(pos int) string {
var row int
switch {
case pos >= 7 && pos <= 9:
row = 2
case pos >= 4 && pos <= 6:
row = 1
case pos >= 1 && pos <= 3:
row = 0
default:
return "wrong position"
}
col := pos - row*3 - 1
if g.board[row][col] != emptyMark {
return "already played!"
}
// put the player
g.board[row][col] = g.player
return ""
}
func (g game) finito() string {
switch {
case g.won():
return fmt.Sprintf("WINNER: %s", g.player)
case g.turn == maxTurn:
return "TIE!"
}
return ""
}
func (g game) won() (won bool) {
for _, m := range [2]string{mark1, mark2} {
b, mmm := g.board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
func switchTo(player string) string {
if player == mark1 {
return mark2
}
return mark1
}

View File

@@ -0,0 +1,166 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
fmt.Println(`
~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~`)
loop(bufio.NewScanner(os.Stdin))
}
const (
// skin options :-)
emptyMark = "☆"
mark1 = "💀"
mark2 = "🎈"
maxTurn = 9
)
type game struct {
board [][]string
turn int
player string
buf strings.Builder
}
func loop(in *bufio.Scanner) {
g := newGame()
for {
g.prompt()
if !in.Scan() {
break
}
// Atoi already return 0 on error; no need to check
// it for the following switch to work
pos, _ := strconv.Atoi(in.Text())
msg := g.play(pos)
if msg != "" {
fmt.Println("\n>>>", msg)
continue
}
if msg := g.finito(); msg != "" {
fmt.Printf("\n%s%s\n", g, msg)
break
}
g.next()
}
}
func newGame() *game {
return &game{
player: mark1,
board: [][]string{
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
{emptyMark, emptyMark, emptyMark},
},
}
}
func (g *game) prompt() {
fmt.Printf("\n%s%s [1-9]: ", g, g.player)
}
func (g *game) String() string {
g.buf.Reset()
g.buf.WriteRune('\n')
g.buf.WriteString("---+----+---\n")
for _, line := range g.board {
g.buf.WriteString(strings.Join(line, " | "))
g.buf.WriteRune('\n')
g.buf.WriteString("---+----+---\n")
}
return g.buf.String()
}
func (g *game) play(pos int) string {
var row int
switch {
case pos >= 7 && pos <= 9:
row = 2
case pos >= 4 && pos <= 6:
row = 1
case pos >= 1 && pos <= 3:
row = 0
default:
return "wrong position"
}
col := pos - row*3 - 1
if g.board[row][col] != emptyMark {
return "already played!"
}
// put the player
g.board[row][col] = g.player
return ""
}
func (g *game) finito() string {
g.turn++
switch {
case g.won():
return fmt.Sprintf("WINNER: %s", g.player)
case g.turn == maxTurn:
return "TIE!"
}
return ""
}
func (g *game) won() (won bool) {
for _, m := range [2]string{mark1, mark2} {
b, mmm := g.board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
func (g *game) next() {
if g.player == mark1 {
g.player = mark2
} else {
g.player = mark1
}
}

View File

@@ -0,0 +1,152 @@
package main
import (
"strings"
)
type state int
const (
maxTurn = 9
wrongPosition = -2
statePlaying state = iota
stateWon
stateTie
stateAlreadyPlayed
stateWrongPosition
)
type game struct {
board [][]string
turn int
player string
skin // embed the skin
logger // embed the logger
}
func newGame(s skin, l logger) *game {
return &game{
player: s.mark1,
board: [][]string{
{s.empty, s.empty, s.empty},
{s.empty, s.empty, s.empty},
{s.empty, s.empty, s.empty},
},
skin: s,
logger: l,
}
}
func (g *game) play(pos int) state {
if st := g.move(pos); st != statePlaying {
return st
}
g.turn++ // increment the turn
// first check the winner then check the tie
// or the last mover won't win
switch {
case g.won():
return stateWon
case g.turn == maxTurn:
return stateTie
}
g.changePlayer()
return statePlaying
}
func (g *game) move(pos int) state {
row, col := position(pos)
if row+col == wrongPosition {
return stateWrongPosition
}
if g.board[row][col] != g.empty {
return stateAlreadyPlayed
}
// put the player's mark on the board
g.board[row][col] = g.player
return statePlaying
}
// we can detect the winning state just by comparing the strings
// because, the game board is a bunch of strings
func (g *game) won() (won bool) {
for _, m := range [2]string{g.mark1, g.mark2} {
b, mmm := g.board, strings.Repeat(m, 3)
won = /* horizontals */
strings.Join(b[0], "") == mmm ||
strings.Join(b[1], "") == mmm ||
strings.Join(b[2], "") == mmm ||
/* verticals */
b[0][0]+b[1][0]+b[2][0] == mmm ||
b[0][1]+b[1][1]+b[2][1] == mmm ||
b[0][2]+b[1][2]+b[2][2] == mmm ||
/* diagonals */
b[0][0]+b[1][1]+b[2][2] == mmm ||
b[0][2]+b[1][1]+b[2][0] == mmm
if won {
return true
}
}
return false
}
// this method should have a pointer receiver
// because, it changes the game value
func (g *game) changePlayer() {
if g.player == g.mark1 {
g.player = g.mark2
} else {
g.player = g.mark1
}
}
func (g *game) print() {
g.Println()
g.Println(g.header)
for i, line := range g.board {
g.Print(g.separator)
for _, m := range line {
g.Printf("%2s%s", m, g.separator)
}
if i+1 != len(g.board) {
g.Printf("\n%s\n", g.middle)
}
}
g.Printf("\n%s\n", g.footer)
}
// this function doesn't depend on the game state
// so, make it a function instead of a method
func position(pos int) (row, col int) {
switch {
case pos >= 1 && pos <= 3:
row = 0
case pos >= 4 && pos <= 6:
row = 1
case pos >= 7 && pos <= 9:
row = 2
default:
return -1, -1
}
return row, pos - row*3 - 1
}

View File

@@ -0,0 +1,19 @@
package main
type logger struct {
print func(...interface{}) (int, error)
printf func(fmt string, args ...interface{}) (int, error)
println func(...interface{}) (int, error)
}
func (l logger) Print(args ...interface{}) {
l.print(args...)
}
func (l logger) Printf(fmt string, args ...interface{}) {
l.printf(fmt, args...)
}
func (l logger) Println(args ...interface{}) {
l.println(args...)
}

View File

@@ -0,0 +1,93 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
c "github.com/fatih/color"
rainbow "github.com/guineveresaenger/golang-rainbow"
)
// TODO: move the main logic into a helper
func main() {
const banner = `
~~~~~~~~~~~~~~~~
TIC~TAC~TOE
~~~~~~~~~~~~~~~~`
in := bufio.NewScanner(os.Stdin)
sk := selectSkin(in)
lg := logger{
print: fmt.Print,
printf: fmt.Printf,
println: fmt.Println,
}
for {
g := newGame(sk, lg)
game:
for {
rainbow.Rainbow(banner, 3)
g.print()
fmt.Printf(c.CyanString("\n%s [1-9]: ", g.player))
if !in.Scan() {
break
}
pos, _ := strconv.Atoi(in.Text())
switch st := g.play(pos); st {
case stateAlreadyPlayed, stateWrongPosition:
announce(g, st)
continue
case stateWon, stateTie:
announce(g, st)
break game
}
g.Print("\033[2J")
}
fmt.Print(c.MagentaString("One more game? [y/n]: "))
if in.Scan(); in.Text() != "y" {
fmt.Println("OK, bye!")
break
}
}
}
func announce(g *game, st state) {
red := c.New(c.FgRed, c.Bold)
green := c.New(c.BgBlack, c.FgGreen, c.Bold)
switch st {
case stateAlreadyPlayed, stateWrongPosition:
red.Printf("\n>>> You can't play there!\n")
case stateWon:
g.print()
green.Printf("\nWINNER: %s\n", g.player)
case stateTie:
g.print()
green.Printf("\nTIE!\n")
}
}
func selectSkin(in *bufio.Scanner) skin {
fmt.Println(c.MagentaString("Our finest selection of skins:"))
for name := range skins {
fmt.Printf("- %s\n", name)
}
fmt.Print(c.GreenString("\nEnter the name of the skin: "))
in.Scan()
if sk, ok := skins[in.Text()]; ok {
return sk
}
return defaultSkin
}

View File

@@ -0,0 +1,5 @@
https://github.com/mattn/go-runewidth
https://github.com/olekukonko/tablewriter
https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c

View File

@@ -0,0 +1,91 @@
package main
import "github.com/fatih/color"
// skin options :-)
type skin struct {
empty, mark1, mark2 string
header, middle, footer, separator string
unicode bool
}
var skins = map[string]skin{
"colorful": colorfulSkin,
"poseidon": poseidonSkin,
"statues": statuesSkin,
"aliens": aliensSkin,
"snow": snowSkin,
"monkeys": monkeysSkin,
}
var defaultSkin = skin{
empty: " ",
mark1: " X ",
mark2: " O ",
header: "┌───┬───┬───┐",
middle: "├───┼───┼───┤",
footer: "└───┴───┴───┘",
separator: "│",
}
var colorfulSkin = skin{
empty: " ",
mark1: color.CyanString(" X "),
mark2: color.HiMagentaString(" O "),
header: color.HiBlueString("┌───┬───┬───┐"),
middle: color.HiBlueString("├───┼───┼───┤"),
footer: color.HiBlueString("└───┴───┴───┘"),
separator: color.BlueString("│"),
}
var poseidonSkin = skin{
empty: "❓ ",
mark1: "🔱 ",
mark2: "⚓️ ",
header: "●————●————●————●",
middle: "●————●————●————●",
footer: "●————●————●————●",
separator: "⎮ ",
}
var statuesSkin = skin{
empty: "❓ ",
mark1: "🗿 ",
mark2: "🗽 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}
var aliensSkin = skin{
empty: "❓ ",
mark1: "👽 ",
mark2: "👾 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}
var snowSkin = skin{
empty: "❓ ",
mark1: "⛄ ",
mark2: "❄️ ",
header: "╔════╦════╦════╗",
middle: "╠════╬════╬════╣",
footer: "╚════╩════╩════╝",
separator: "║ ",
}
var monkeysSkin = skin{
empty: "🍌 ",
mark1: "🙈 ",
mark2: "🙉 ",
header: "┌────┬────┬────┐",
middle: "├────┼────┼────┤",
footer: "└────┴────┴────┘",
separator: "│ ",
}

View File

@@ -0,0 +1,3 @@
**Check out:**
Tic-Tac-Toe Gist:
https://gist.github.com/agalal/bff3cb779c337274d2a3462c630f4d74

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org 4
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org -100
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org FOUR
golang.org 6
blog.golang.org 20
blog.golang.org 10