← Home
package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"sort"
	"strings"
)

const N = 1000000
const eps = 1e-9

type IPoint struct {
	x, y int
}

type FPoint struct {
	x, y float64
}

func readLine(r *bufio.Reader) string {
	for {
		s, err := r.ReadString('\n')
		if err != nil && len(s) == 0 {
			os.Exit(0)
		}
		s = strings.TrimSpace(s)
		if s != "" {
			return s
		}
		if err != nil {
			os.Exit(0)
		}
	}
}

func query(w *bufio.Writer, r *bufio.Reader, p IPoint) (string, bool) {
	fmt.Fprintf(w, "%d %d\n", p.x, p.y)
	w.Flush()
	s := readLine(r)
	if strings.Contains(s, "!") {
		return s, true
	}
	return s, false
}

func dot(a, b FPoint) float64 {
	return a.x*b.x + a.y*b.y
}

func area(poly []FPoint) float64 {
	n := len(poly)
	if n < 3 {
		return 0
	}
	s := 0.0
	for i := 0; i < n; i++ {
		j := (i + 1) % n
		s += poly[i].x*poly[j].y - poly[j].x*poly[i].y
	}
	if s < 0 {
		s = -s
	}
	return s * 0.5
}

func bbox(poly []FPoint) (float64, float64, float64, float64) {
	if len(poly) == 0 {
		return 0, 0, 0, 0
	}
	minx, maxx := poly[0].x, poly[0].x
	miny, maxy := poly[0].y, poly[0].y
	for _, p := range poly {
		if p.x < minx {
			minx = p.x
		}
		if p.x > maxx {
			maxx = p.x
		}
		if p.y < miny {
			miny = p.y
		}
		if p.y > maxy {
			maxy = p.y
		}
	}
	return minx, maxx, miny, maxy
}

func centroid(poly []FPoint) FPoint {
	n := len(poly)
	if n < 3 {
		minx, maxx, miny, maxy := bbox(poly)
		return FPoint{(minx + maxx) * 0.5, (miny + maxy) * 0.5}
	}
	a2 := 0.0
	cx, cy := 0.0, 0.0
	for i := 0; i < n; i++ {
		j := (i + 1) % n
		cr := poly[i].x*poly[j].y - poly[j].x*poly[i].y
		a2 += cr
		cx += (poly[i].x + poly[j].x) * cr
		cy += (poly[i].y + poly[j].y) * cr
	}
	if math.Abs(a2) < 1e-12 {
		minx, maxx, miny, maxy := bbox(poly)
		return FPoint{(minx + maxx) * 0.5, (miny + maxy) * 0.5}
	}
	return FPoint{cx / (3 * a2), cy / (3 * a2)}
}

func clip(poly []FPoint, n FPoint, c float64, keepPos bool) []FPoint {
	if len(poly) == 0 {
		return nil
	}
	res := make([]FPoint, 0, len(poly)+2)
	inside := func(v float64) bool {
		if keepPos {
			return v >= -eps
		}
		return v <= eps
	}
	for i := 0; i < len(poly); i++ {
		a := poly[i]
		b := poly[(i+1)%len(poly)]
		va := dot(n, a) - c
		vb := dot(n, b) - c
		ina := inside(va)
		inb := inside(vb)
		if ina && inb {
			res = append(res, b)
		} else if ina && !inb {
			t := va / (va - vb)
			res = append(res, FPoint{
				a.x + (b.x-a.x)*t,
				a.y + (b.y-a.y)*t,
			})
		} else if !ina && inb {
			t := va / (va - vb)
			res = append(res, FPoint{
				a.x + (b.x-a.x)*t,
				a.y + (b.y-a.y)*t,
			})
			res = append(res, b)
		}
	}
	if len(res) >= 2 {
		tmp := make([]FPoint, 0, len(res))
		for i := 0; i < len(res); i++ {
			j := (i + 1) % len(res)
			if math.Hypot(res[i].x-res[j].x, res[i].y-res[j].y) > 1e-10 {
				tmp = append(tmp, res[i])
			}
		}
		res = tmp
	}
	return res
}

func pointInPoly(poly []FPoint, p IPoint) bool {
	if len(poly) == 0 {
		return false
	}
	x := float64(p.x)
	y := float64(p.y)
	sign := 0
	for i := 0; i < len(poly); i++ {
		a := poly[i]
		b := poly[(i+1)%len(poly)]
		cr := (b.x-a.x)*(y-a.y) - (b.y-a.y)*(x-a.x)
		if math.Abs(cr) <= 1e-7 {
			continue
		}
		if cr > 0 {
			if sign < 0 {
				return false
			}
			sign = 1
		} else {
			if sign > 0 {
				return false
			}
			sign = -1
		}
	}
	return true
}

func sqdist(a, b IPoint) int64 {
	dx := int64(a.x - b.x)
	dy := int64(a.y - b.y)
	return dx*dx + dy*dy
}

func consistent(t IPoint, ori int, pts []IPoint, resps []string, sameLabel, labelA, labelB string) bool {
	if len(pts) == 0 {
		return true
	}
	if t == pts[0] {
		return false
	}
	for i := 1; i < len(pts); i++ {
		if t == pts[i] {
			return false
		}
		d1 := sqdist(t, pts[i-1])
		d2 := sqdist(t, pts[i])
		if d1 == d2 {
			if resps[i] != sameLabel {
				return false
			}
		} else {
			closer := labelA
			further := labelB
			if ori == 1 {
				closer, further = labelB, labelA
			}
			if d2 < d1 {
				if closer == "" || resps[i] != closer {
					return false
				}
			} else {
				if further == "" || resps[i] != further {
					return false
				}
			}
		}
	}
	return true
}

func enumerateCandidates(polys [2][]FPoint, pts []IPoint, resps []string, sameLabel, labelA, labelB string, limitScan int) []IPoint {
	type node struct {
		poly []FPoint
		ori  int
	}
	items := []node{
		{polys[0], 0},
		{polys[1], 1},
	}
	seen := map[[2]int]bool{}
	ans := make([]IPoint, 0)
	for _, it := range items {
		if len(it.poly) == 0 {
			continue
		}
		minx, maxx, miny, maxy := bbox(it.poly)
		lx := int(math.Ceil(minx - 2.0))
		rx := int(math.Floor(maxx + 2.0))
		ly := int(math.Ceil(miny - 2.0))
		ry := int(math.Floor(maxy + 2.0))
		if lx < 0 {
			lx = 0
		}
		if ly < 0 {
			ly = 0
		}
		if rx > N {
			rx = N
		}
		if ry > N {
			ry = N
		}
		if lx > rx || ly > ry {
			continue
		}
		areaBox := int64(rx-lx+1) * int64(ry-ly+1)
		if areaBox > int64(limitScan) {
			return nil
		}
		for x := lx; x <= rx; x++ {
			for y := ly; y <= ry; y++ {
				p := IPoint{x, y}
				if !pointInPoly(it.poly, p) {
					continue
				}
				if !consistent(p, it.ori, pts, resps, sameLabel, labelA, labelB) {
					continue
				}
				k := [2]int{x, y}
				if !seen[k] {
					seen[k] = true
					ans = append(ans, p)
				}
			}
		}
	}
	sort.Slice(ans, func(i, j int) bool {
		if ans[i].x != ans[j].x {
			return ans[i].x < ans[j].x
		}
		return ans[i].y < ans[j].y
	})
	return ans
}

func chooseNext(cur IPoint, polys [2][]FPoint) IPoint {
	P, Q := polys[0], polys[1]
	areaP := area(P)
	areaQ := area(Q)

	cands := make([]IPoint, 0, 1024)
	used := map[[2]int]bool{}
	add := func(x, y int) {
		if x < 0 || x > N || y < 0 || y > N {
			return
		}
		if x == cur.x && y == cur.y {
			return
		}
		if ((x+y)&1) == ((cur.x+cur.y)&1) {
			return
		}
		k := [2]int{x, y}
		if used[k] {
			return
		}
		used[k] = true
		cands = append(cands, IPoint{x, y})
	}

	grid := 20
	for i := 0; i <= grid; i++ {
		x := int(math.Round(float64(N) * float64(i) / float64(grid)))
		for j := 0; j <= grid; j++ {
			y := int(math.Round(float64(N) * float64(j) / float64(grid)))
			add(x, y)
		}
	}

	cs := []FPoint{centroid(P), centroid(Q)}
	for _, poly := range polys {
		if len(poly) == 0 {
			continue
		}
		minx, maxx, miny, maxy := bbox(poly)
		cs = append(cs, FPoint{(minx + maxx) * 0.5, (miny + maxy) * 0.5})
		for _, v := range poly {
			cs = append(cs, v)
		}
	}
	for _, c := range cs {
		rx := int(math.Round(2*c.x - float64(cur.x)))
		ry := int(math.Round(2*c.y - float64(cur.y)))
		for dx := -2; dx <= 2; dx++ {
			for dy := -2; dy <= 2; dy++ {
				add(rx+dx, ry+dy)
			}
		}
	}
	add(0, 0)
	add(0, N)
	add(N, 0)
	add(N, N)
	add(cur.x^1, cur.y)
	add(cur.x, cur.y^1)

	best := IPoint{(cur.x + 1) % (N + 1), cur.y}
	if ((best.x+best.y)&1) == ((cur.x+cur.y)&1) {
		best.y ^= 1
		if best.y < 0 || best.y > N {
			best.y = cur.y
			best.x ^= 1
		}
	}
	bestScore := math.Inf(1)

	for _, b := range cands {
		n := FPoint{float64(b.x - cur.x), float64(b.y - cur.y)}
		c := (float64(b.x*b.x+b.y*b.y) - float64(cur.x*cur.x+cur.y*cur.y)) * 0.5
		ap := area(clip(P, n, c, true))
		aqm := area(clip(Q, n, c, false))
		t1 := ap + aqm
		t2 := (areaP - ap) + (areaQ - aqm)
		score := math.Max(t1, t2)
		if score+1e-7 < bestScore {
			bestScore = score
			best = b
		}
	}
	return best
}

func updatePolys(cur, next IPoint, resp string, sameLabel, labelA string, polys *[2][]FPoint) {
	if resp == sameLabel {
		return
	}
	n := FPoint{float64(next.x - cur.x), float64(next.y - cur.y)}
	c := (float64(next.x*next.x+next.y*next.y) - float64(cur.x*cur.x+cur.y*cur.y)) * 0.5
	if resp == labelA {
		polys[0] = clip(polys[0], n, c, true)
		polys[1] = clip(polys[1], n, c, false)
	} else {
		polys[0] = clip(polys[0], n, c, false)
		polys[1] = clip(polys[1], n, c, true)
	}
}

func main() {
	in := bufio.NewReaderSize(os.Stdin, 1<<20)
	out := bufio.NewWriterSize(os.Stdout, 1<<20)
	defer out.Flush()

	square := []FPoint{{0, 0}, {float64(N), 0}, {float64(N), float64(N)}, {0, float64(N)}}
	polys := [2][]FPoint{append([]FPoint{}, square...), append([]FPoint{}, square...)}

	cur := IPoint{N / 2, N / 2}
	resp, found := query(out, in, cur)
	if found {
		return
	}
	pts := []IPoint{cur}
	resps := []string{resp}

	resp, found = query(out, in, cur)
	if found {
		return
	}
	sameLabel := resp
	pts = append(pts, cur)
	resps = append(resps, resp)

	labelA := ""
	labelB := ""
	queries := 2

	process := func(next IPoint, resp string) {
		if resp != sameLabel {
			if labelA == "" {
				labelA = resp
			} else if resp != labelA && labelB == "" {
				labelB = resp
			}
			updatePolys(cur, next, resp, sameLabel, labelA, &polys)
		}
		cur = next
		pts = append(pts, next)
		resps = append(resps, resp)
	}

	for queries < 60 {
		cands := enumerateCandidates(polys, pts, resps, sameLabel, labelA, labelB, 50000)
		if len(cands) > 0 && len(cands) <= 64-queries {
			for _, p := range cands {
				resp, found = query(out, in, p)
				queries++
				if found {
					return
				}
				process(p, resp)
				if queries >= 64 {
					return
				}
			}
			continue
		}

		next := chooseNext(cur, polys)
		resp, found = query(out, in, next)
		queries++
		if found {
			return
		}
		process(next, resp)
	}

	cands := enumerateCandidates(polys, pts, resps, sameLabel, labelA, labelB, 300000)
	if len(cands) == 0 {
		minx0, maxx0, miny0, maxy0 := bbox(polys[0])
		minx1, maxx1, miny1, maxy1 := bbox(polys[1])
		guess := []IPoint{
			{int(math.Round((minx0 + maxx0) * 0.5)), int(math.Round((miny0 + maxy0) * 0.5))},
			{int(math.Round((minx1 + maxx1) * 0.5)), int(math.Round((miny1 + maxy1) * 0.5))},
			{0, 0},
			{0, N},
			{N, 0},
			{N, N},
		}
		used := map[[2]int]bool{}
		for _, p := range guess {
			if p.x < 0 {
				p.x = 0
			}
			if p.x > N {
				p.x = N
			}
			if p.y < 0 {
				p.y = 0
			}
			if p.y > N {
				p.y = N
			}
			k := [2]int{p.x, p.y}
			if used[k] {
				continue
			}
			used[k] = true
			cands = append(cands, p)
		}
	}
	for _, p := range cands {
		if queries >= 64 {
			return
		}
		_, found = query(out, in, p)
		queries++
		if found {
			return
		}
	}
}