Server Implementation
Download the Anthill-Server.
To test your Anthill-Bot it might be convenient for you to use our Anthill-Server. Note that it is work in progress and in case you find any mistakes, please report them either via the Forum or directly to sandro@soi.ch.
The server is written in C++ and should work under both Linux and Windows (and Mac, for that matter). However, the focus lies on Linux. In order to compile it has to be linked against the NCurses library (resp. its windows port PDCurses), which is used to produce a terminal-based-gui-visualization.
Hopefully the following information will help you getting the server running, if you have any problems, however, do not hesitate to either write in the Forum or send an email to sandro@soi.ch
Compilation under Linux
Make sure you have installed gcc and install the ncurses-library. Under Ubuntu this can be done by:
sudo apt-get install build-essential libncurses5-dev
Then compile the server with
g++ -o server server.cpp -lncurses
Compilation under Windows
We managed to compile the server under Windows using different Versions of Microsof Visual Studio (if you have any reports with other compilers, please contact us). First download PDCurses and extract it in some folder. Then start Visual Studio, create an empty project and add server.cpp as an «existing item». Also add curses.h, such that the header-file is found during compilation. Moreover make sure that you link against the pdcurses.lib by adding it to «Project - Properties- Configuration Properties - Linker - Input - Additional Dependencies». Moreover when testing make sure you have set up the arguments correctly (this can be done in «Project - Properties - Configuration Properties - Debugging - Command Arguments») and that the pdcurses.dll lies in the same folder as your executable (this is probably the Debug folder).
After that you should be good to go.
Usage
The server is started with
./server MODE W H K N Z S "commands for starting bot1" "commands for starting bot2" ...
where
MODE is:
- 0 for Simulation mode (simulate game and print ranking).
- 1 for Step-By-Step Simulation
- 2 for Terminal-GUI-Mode.
and the other values are desribed in the task description.
So a sample execution could be
./server 2 50 60 3 20 18 1 ./bot1 "java bot2" "python bot3.py" "ruby bot4.rb"
assuming the bots are in the same folder and one of the bots is compiled to
bot1.
Note that in either MODE a file with log_file.txt is created which stores debug-information and potential errors. If anything goes wrong please consult the log_file.txt first and also append it to your potential emails/Forum-posts.
Sample Bot(s)
Besides the Server implementation we provide you with a sample bot to get started with. The bot acts not very intelligently, it just walks around randomly until it sees its own hill, at which point it marks the current position with a scent and proceeds walking towards the hill. Moreover if it does not see the hill but a position marked with the scent, it walks towards it.
We implemented this strategy in different programming languages, to get you started easily and to show you that the programming language does not really matter.
If your favorite language is missing just request it in the forum and we will try to port the sample bot to your requested language (as long as it is doable in some reasonable effort, so porting to some Assembly-Language or Brainfuck might be out of scope. This of course does not mean that you are not allowed to hand-in in Brainfuck ).
Currently we have ported the sample bot to the following languages: Ada, C, C++, D, Fortran90, Go, Haskell, Java, Python, Ruby.
Also note that we are not experts in all those languages. So it is very well possible that there are some errors or at least non-best-practices in there. If you happen to find one, send an email to sandro@soi.ch.
Ada - Version
-- sample bot in ada
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Numerics.Discrete_Random;
with Ada.Characters.Handling; use Ada.Characters.Handling;
procedure Bot is
type View is range -3..3;
type Move is (N,S,W,E,H,M,J); subtype Direction is Move range N..E;
package Rand is new Ada.Numerics.Discrete_Random(Direction); use Rand;
Gen: Generator;
function MoveTowards (Y, X: View) return Move is
begin
if Y<0 then return N;
elsif Y>0 then return S;
elsif X<0 then return W;
elsif X>0 then return E;
else return H; end if;
end MoveTowards;
-- field of vision
Map1: array (View, View) of Character;
Map2: array (View, View) of Natural;
NextMove, LastMove: Move;
HillDiscovered, MarkerDiscovered: Boolean;
Marker: Natural;
GoalX, GoalY: View;
Width, Height, K, Teams, Size, Ants, Z, Dx, Dy: Integer;
I, Hill: Character;
begin
Reset (Gen);
-- read game specifications
Get(Width); Get(Height); Get(K); Get(Ants); Get(Z); Get(Teams); Get(Size);
Get(I); Get(I); Hill := To_Upper (I); Marker := Character'Pos (I);
Skip_Line;
loop
-- read last move
Get(LastMove);
if LastMove = J then
-- jump, read offset
Get(Dx); Get(Dy);
end if;,
-- read field of vision
HillDiscovered := False;
for Y in View loop
for X in View loop
Get (Map1(Y, X));
if Map1(Y, X) = Hill then
GoalX := X; GoalY := Y;
HillDiscovered := True;
end if;
end loop;
Skip_Line;
end loop;
MarkerDiscovered := False;
for Y in View loop
for X in View loop
Get (Map2 (Y, X));
if not HillDiscovered and Map2(Y, X) = Marker then
GoalX := X; GoalY := Y;
MarkerDiscovered := True;
end if;
end loop;
end loop;
Skip_Line;
-- test if we are finished
exit when Map1(0,0)='.';
if HillDiscovered or MarkerDiscovered then
-- goal in sight
-- test whether current position needs to be marked
if Map2(0,0)=Marker or MarkerDiscovered then
-- then move towards the hill
NextMove := MoveTowards (GoalY, GoalX);
else
-- otherwise mark the position
NextMove := M;
end if;
else
-- neither marked position nor hill in sight
-- choose a random move
NextMove := Random (Gen);
end if;
-- output move
Put (Move'Image (NextMove));
if NextMove = M then
Put (Marker);
end if;
New_Line;
Flush;
end loop;
end Bot;
C - Version
#include <stdio.h>
#include <stdlib.h>
char directions
[]={'N',
'S',
'W',
'E'};
char move_towards
(int y,
int x
) {
if (y<
3) return directions
[0];
else if (y>
3) return directions
[1];
else if (x<
3) return directions
[2];
else if (x>
3) return directions
[3];
return 'H';
}
char move_random
() {
return directions
[rand
()%4];
}
int main
() {
int W,H,K,N,Z,V,S,map2
[10][10],scent,goaly,goalx,y,x;
char I,buf
[10],map1
[10][10],move,see_goal;
scanf
("%i%i%i%i%i%i%i %c ",
&W,
&H,
&K,
&N,
&Z,
&V,
&S,
&I
);
while(1) {
// Read last move.
gets
(buf
);
// Read first map.
for (y
=0;y<
7;y
++)
gets
(map1
[y
]);
// Read second map.
for (y
=0;y<
7;y
++) {
for (x
=0;x<
7;x
++) {
scanf
("%i",
&map2
[y
][x
]);
}
}
// Read newline.
gets
(buf
);
// Test if we are finished.
if (map1
[3][3]=='.')
return 0;
// Determine move.
scent
=-1;
// Step 1: Look for our hill.
see_goal
= 0;
for (y
=0;y<
7;y
++) {
for (x
=0;x<
7;x
++) {
if (map1
[y
][x
]==toupper
(I
)) {
goalx
= x;
goaly
= y;
see_goal
=1;
}
}
}
if (see_goal
) {
// Hill in sight.
// Test if current position alread marked.
if (map2
[3][3]==(int)I
) {
// Then move towards hill.
move
= move_towards
(goaly,goalx
);
} else {
// Otherwise mark position.
move
='M';
scent
=(int)I;
}
} else {
// Hill not in sight.
// Test if marked position in sight.
for (y
=0;y<
7;y
++) {
for (x
=0;x<
7;x
++) {
if (map2
[y
][x
]==(int)I
) {
goalx
= x;
goaly
= y;
see_goal
=1;
}
}
}
if (see_goal
) {
// Marked position in sight.
// Move towards marked position.
move
= move_towards
(goaly,goalx
);
} else {
// No marked position in sight.
// Choose a random move.
move
= move_random
();
}
}
// Send Move.
if (move
== 'M') {
printf("%c %i\n",move,scent
);
} else {
printf("%c\n",move
);
}
fflush
(stdout
);
}
return 0;
}
C++ - Version
#include <iostream>
#include <vector>
#include <string>
#include <stdlib.h>
using namespace std;
char directions[]={'N','S','W','E'};
char move_towards(int y, int x) {
if (y<3) return directions[0];
else if (y>3) return directions[1];
else if (x<3) return directions[2];
else if (x>3) return directions[3];
return 'H';
}
char move_random() {
return directions[rand()%4];
}
int main() {
int W,H,K,N,Z,V,S;
char I;
string tmp;
cin >> W >> H >> K >> N >> Z >> V >> S >> I;
// Read newline.
getline(cin, tmp);
while(1) {
// Read last move. Not extracting distance information for J-Moves.
string last_move;
getline(cin, last_move);
// Read first map.
vector<string> map1;
for (int y=0;y<7;y++) {
getline(cin, tmp);
map1.push_back(tmp);
}
// Read second map.
vector<vector<int> > map2(7, vector<int>(7));
for (int y=0;y<7;y++) {
for (int x=0;x<7;x++) {
cin >> map2[y][x];
}
}
// Test if we are finished.
if (map1.at(3).at(3)=='.') {
return 0;
}
// Read newline.
getline(cin, tmp);
// Determine move.
int scent,goaly,goalx;
scent=-1;
// Step 1: Look for our hill.
bool see_goal=false;
for (int y=0;y<7;y++) {
for (int x=0;x<7;x++) {
if (map1[y][x]==toupper(I)) {
goalx = x;
goaly = y;
see_goal=true;
}
}
}
char move;
if (see_goal) {
// Hill in sight.
// Test if current position alread marked.
if (map2[3][3]==(int)I) {
// Then move towards hill.
move = move_towards(goaly,goalx);
} else {
// Otherwise mark position.
move='M';
scent=(int)I;
}
} else {
// Hill not in sight.
// Test if marked position in sight.
for (int y=0;y<7;y++) {
for (int x=0;x<7;x++) {
if (map2[y][x]==(int)I) {
goalx = x;
goaly = y;
see_goal=1;
}
}
}
if (see_goal) {
// Marked position in sight.
// Move towards marked position.
move = move_towards(goaly,goalx);
} else {
// No marked position in sight.
// Choose a random move.
move = move_random();
}
}
// Send Move.
// endl automatically flushes.
if (move == 'M') {
cout << move << " " << scent << endl;
} else {
cout << move << endl;
}
}
return 0;
}
D - Version
import std.stdio, std.random, std.conv, std.string;
char moveTowards(size_t y, size_t x) {
return y<3 ? 'N' :
y>3 ? 'S' :
x<3 ? 'W' :
x>3 ? 'E' :
'H' ;
}
char moveRandom() {
return "NSWE"[uniform(0,$)];
}
void main() {
int W,H,K,N,Z,V,S;
char I;
scanf("%d%d%d%d%d%d%d %c ",&W,&H,&K,&N,&Z,&V,&S,&I);
loop: for(;; stdout.flush()) {
// read last move
readln();
// read first map
char[7][7] map1;
foreach(ref l; map1) l=readln()[0..7];
// read second map
int[7][7] map2;
foreach(ref l; map2) l=to!(int[])(readln().strip().split());
// test if we are finished
if(map1[3][3] == '.') return;
// look for hill
foreach(y, l; map1) {
foreach(x, v; l) {
if(v == I+('A'-'a')) {
// seen hill, test if position already marked
if(map2[3][3] == I) {
// then move towards hill
writeln(moveTowards(y,x));
} else {
// otherwise mark position
writeln("M ", to!(int)(I));
}
continue loop;
}
}
}
// hill not in sight
// test if marked position in sight
foreach(y, l; map2) {
foreach(x, v; l) {
if(v == I) {
// marked position in sight
// move towards marked position
writeln(moveTowards(y,x));
continue loop;
}
}
}
// no marked position in sight
// choose a random move
writeln(moveRandom());
}
}
Fortran - Version
! sample bot in FORTRAN 90
program bot
implicit none
character :: moveTowards, randomMove
integer :: Width, Height, K, Teams, Size, Ants, Z
character :: I, Hill, lastMove, nextMove
logical :: hillDiscovered, markerDiscovered
character, dimension(7,7) :: map1
integer, dimension(7,7) :: map2
integer :: x, y, goalX, goalY, marker, dx, dy
call random_seed
! read game specifications
read(*,*) Width, Height, K, Ants, Z, Teams, Size, I
marker = iachar(I); Hill = achar(iachar(I)-32)
do
read(*,*) LastMove
if (LastMove=='J') then
! jump, get offset
read(*,*) dx, dy
end if
! read field of vision
hillDiscovered = .false.
do y=1,7
read(*,'(7A1)') map1(y,1:7)
do x=1,7
if (map1(y,x)==Hill) then
hillDiscovered = .true.
goalX = x; goalY = y
end if
end do;
end do
markerDiscovered = .false.
do y=1,7
read(*,*) map2(y,1:7)
do x=1,7
if (.not. hillDiscovered .and. map2(y,x)==marker) then
markerDiscovered = .true.
goalX = x; goalY = y
end if
end do
end do
! test if we are finished
if (map1(4,4)=='.') exit
markerDiscovered = .false.
if (hillDiscovered .or. markerDiscovered) then
! goal in sight, test whether current position needs to be marked
if (map2(4,4)==marker .or. markerDiscovered) then
! then move towards the hill
nextMove = moveTowards(goalY,goalX)
else
! otherwise mark the position
nextMove = 'M'
end if
else
! neither marked position nor hill in sight, choose a random move
nextMove = randomMove()
end if
! output move
if (nextMove == 'M') then
write(*,'(A1,1X,I3)') 'M', marker
else
write(*,'(A1)') nextMove
end if
call flush
end do
end program bot
character function moveTowards(y, x)
implicit none
integer, intent(in) :: x, y
if (y<4) then; moveTowards = 'N'
elseif (y>4) then; moveTowards = 'S'
elseif (x<4) then; moveTowards = 'W'
elseif (x>4) then; moveTowards = 'E'
else ; moveTowards = 'N'; end if
return
end function moveTowards
character function randomMove()
implicit none
character, dimension(4) :: move = (/'N', 'S', 'W', 'E' /)
real :: r; call random_number(r);
randomMove = move(int(3*r+1))
return
end function randomMove
Go - Version
// sample bot in go
package main
import ("fmt"; "rand")
func moveTowards (y,x int) byte {
switch {
case y<3: return 'N'
case y>3: return 'S'
case x<3: return 'W'
case x>3: return 'E'
}
return 'H'
}
func moveRandom() byte {
return "NSWE"[rand.Intn(4)]
}
func main() {
var ( W,H,K,N,V,Z,S,goalX,goalY,marker,dx,dy int
I,nextMove,lastMove,Hill byte
m1 [7]string
m2 [7][7]int
seeHill, seeMarker bool
)
fmt.Scanf ("%d%d%d%d%d%d%d %c\n", &W,&H,&K,&N,&Z,&V,&S,&I)
Hill = (I+'A')-'a'
marker = int(I)
for {
// read last move
fmt.Scanf ("%c", &lastMove)
if lastMove == 'J' {
// jump, read offset
fmt.Scan (&dx, &dy)
}
// read first map
seeHill = false
for y:=0;y<7;y++ {
fmt.Scan (&m1[y])
for x:=0;x<7;x++ {
// look for the hill
if m1[y][x]==Hill {
seeHill = true
goalX = x; goalY = y
}
}
}
// read second map
seeMarker = false
for y:=0;y<7;y++ {
for x:=0;x<7;x++ {
fmt.Scan (&m2[y][x])
// look for markers
if !seeHill && m2[y][x]==marker {
seeMarker = true
goalX = x; goalY = y
}
}
}
fmt.Scanln()
// test whether we are finished
if m1[3][3]=='.' {
break
}
if seeHill || seeMarker {
// goal in sight, test whether current position needs to be marked
if m2[3][3]==marker || seeMarker {
nextMove = moveTowards (goalY, goalX)
} else {
nextMove = 'M'
}
} else {
// neither marked position nor hill in sight, choose random move
nextMove = moveRandom()
}
// send move
if nextMove == 'M' {
fmt.Printf ("M %d\n", marker)
} else {
fmt.Printf ("%c\n", nextMove)
}
}
}
Haskell - Version
import System
.IO
import Data
.List
import Data
.Char
import System
.Random
moveTowards y x
| y
< 3 = 'N'
| y
> 3 = 'S'
| x
< 3 = 'W'
| x
> 3 = 'E'
moveTowards
_ _ = 'H'
moveRandom x
= "NSWE"!!x
find2D i yss
= let loop y
[] = Nothing
loop y
(xs:xss
) = case elemIndex i xs
of
Just x
-> Just
(y,x
)
_ -> loop
(y
+1) xss
in loop
0 yss
bot i
(lines, r
) = let maps
= drop 1 lines
map1
= take 7 maps
map2
= map (map (\x
-> read x
:: Int)) (map words (drop 7 maps
))
pos1
= find2D
(toUpper i
) map1
pos2
= find2D
(ord i
) map2
move
= case pos1
of
Just
(y,x
) -> if map2
!!3!!3 == ord i
then (moveTowards y x
):
"\n"
else "M " ++ show (ord i
) ++"\n"
_ -> case pos2
of
Just
(y,x
) -> (moveTowards y x
):
"\n"
_ -> (moveRandom r
):
"\n"
in if map1
!!3!!3 /= '
.'
then move
else ""
chunks n
[] = []
chunks n xs
= (take n xs
):
(chunks n
(drop n xs
))
concatWhile p
(xs:xss
) | p xs
= xs
++ concatWhile p xss
concatWhile
_ _ = []
feed f rs input
= concatWhile
(/="") (map f
(zip (chunks
15 (lines input
)) rs
))
main
= do
hSetBuffering stdout LineBuffering
l
<- getLine
gen
<- newStdGen
let rs
= randomRs
(0,
3) gen
let w
= words l
let i
= w
!!7!!0
interact (feed
(bot i
) rs
)
Java - Version
import java.util.*;
import java.lang.*;
import java.io.*;
class bot
{
public static Random r
;
public static char[] directions=
{'N',
'S',
'W',
'E'};
public static char move_towards
(int y,
int x
) {
if (y
<3) return directions
[0];
else if (y
>3) return directions
[1];
else if (x
<3) return directions
[2];
else if (x
>3) return directions
[3];
return 'H';
}
public static char move_random
() {
return directions
[r.
nextInt(4)];
}
public static char toUpper
(char x
) {
if (x
>=
'a' && x
<=
'z') return (char)((int)x+
'A'-
'a');
return x
;
}
public static void main
(String[] args
) throws IOException {
r =
new Random(123);
BufferedReader in =
new BufferedReader(new InputStreamReader(System.
in));
int W,H,K,N,Z,V,S
;
int[][] map2 =
new int[7][7];
char I
;
String[] map1 =
new String[7];
String line = in.
readLine();
Scanner s =
new Scanner
(line
).
useDelimiter(" ");
W=s.
nextInt();
H=s.
nextInt();
K=s.
nextInt();
N=s.
nextInt();
Z=s.
nextInt();
V=s.
nextInt();
S=s.
nextInt();
I=line.
charAt(line.
length()-1);
while (true) {
// Read last move. Ignoring distance-information for J-Moves.
line = in.
readLine();
char last_move = line.
charAt(0);
// Read first map.
for (int y=
0;y
<7;y++
)
map1
[y
]=in.
readLine();
// Read second map.
for (int y=
0;y
<7;y++
) {
line = in.
readLine();
s =
new Scanner
(line
).
useDelimiter(" ");
for (int x=
0;x
<7;x++
) {
map2
[y
][x
]=s.
nextInt();
}
}
// Test if we are finished.
if (map1
[3].
charAt(3)==
'.')
return;
// Determine move.
char move
;
int scent=
-1;
// Step 1: Look for our hill.
int goalx,goaly
;
goalx=goaly=
0;
boolean see_goal =
false;
for (int y=
0;y
<7;y++
) {
for (int x=
0;x
<7;x++
) {
if (map1
[y
].
charAt(x
)==toUpper
(I
)) {
goalx = x
;
goaly = y
;
see_goal=
true;
}
}
}
if (see_goal
) {
// Hill in sight.
// Test if current position alread marked.
if (map2
[3][3]==
(int)I
) {
// Then move towards hill.
move = move_towards
(goaly,goalx
);
} else {
// Otherwise mark position.
move=
'M';
scent=
(int)I
;
}
} else {
// Hill not in sight.
// Test if marked position in sight.
for (int y=
0;y
<7;y++
) {
for (int x=
0;x
<7;x++
) {
if (map2
[y
][x
]==
(int)I
) {
goalx = x
;
goaly = y
;
see_goal=
true;
}
}
}
if (see_goal
) {
// Marked position in sight.
// Move towards marked position.
move = move_towards
(goaly,goalx
);
} else {
// No marked position in sight.
// Choose a random move.
move = move_random
();
}
}
// Send Move.
if (move ==
'M') {
System.
out.
println(move +
" " + scent
);
} else {
System.
out.
println(move
);
}
System.
out.
flush();
}
}
}
Python - Version
import sys
import random
directions=['N','S','W','E']
def move_towards(y,x):
global directions
if y<3:
return directions[0]
elif y>3:
return directions[1]
elif x<3:
return directions[2]
elif x>3:
return directions[3]
return 'H'
def move_random():
return directions[random.randint(0,3)]
l = sys.stdin.readline()
[W,H,K,N,Z,V,S,I] = l[:-1].split(' ')
while True:
# Read last move. Ignoring jumping information for J-Moves.
last_move = sys.stdin.readline()[0]
# Read first map.
map1 = [[c for c in sys.stdin.readline().strip()] for y in xrange(7)]
# Read second map.
map2 = [[int(c) for c in sys.stdin.readline().strip().split(' ')] for y in xrange(7)]
# Test if we are finished.
if map1[3][3] == '.':
exit(0)
# Determine move.
see_goal = False
for y,l in enumerate(map1):
for x,v in enumerate(l):
if v == I.upper():
goaly,goalx=y,x
see_goal = True
break
if see_goal:
# Hill in sight.
# Test if current position alread marked.
if map2[3][3] == ord(I):
# Then move towards hill.
move = move_towards(goaly,goalx)
else:
# Otherwise mark position.
move='M'
scent=ord(I)
else:
# Hill not in sight.
# Test if marked position in sight.
for y,l in enumerate(map2):
for x,v in enumerate(l):
if v == ord(I):
goaly,goalx=y,x
see_goal = True
break
if see_goal:
# Marked position in sight.
# Move towards marked position.
move = move_towards(goaly,goalx)
else:
# No marked position in sight.
# Choose a random move.
move = move_random()
# Send Move.
if move == 'M':
print move,scent
else:
print move
sys.stdout.flush()
Ruby - Version
$directions=['N','S','W','E']
def move_towards(y,x)
if y<3
return $directions[0]
elsif y>3
return $directions[1]
elsif x<3
return $directions[2]
elsif x>3
return $directions[3]
end
return 'H'
end
def move_random()
return $directions[rand(4)]
end
srand(123)
l = $stdin.readline()
W,H,K,N,Z,V,S,I = l.split(' ')[0], l.split(' ')[1].to_i, l.split(' ')[2].to_i, l.split(' ')[3].to_i, l.split(' ')[4].to_i, l.split(' ')[5].to_i, l.split(' ')[6].to_i, l.split(' ')[7][0]
while true
# Read last move. Ignoring jumping information for J-Moves.
last_move = $stdin.readline()[0]
# Read first map.
map1 = Array.new
(0..6).each {map1 << $stdin.readline().strip.each_char.to_a}
# Read second map.
map2 = Array.new
(0..6).each {|y| map2 << $stdin.readline().strip.split(' ').each {|v| v.to_i }}
# Test if we are finished.
if map1[3][3] == '.'
exit(0)
end
goaly,goalx,see_goal=-1,-1,false
# Determine move.
map1.each_with_index { |row,y|
row.each_with_index { |el,x|
if el==I.chr.to_s.capitalize[0].chr
goaly,goalx=y,x
see_goal=true
end
}
}
if see_goal
# Hill in sight.
# Test if current position alread marked.
if map2[3][3].to_i == I.to_i
# Then move towards hill.
move = move_towards(goaly,goalx)
else
# Otherwise mark position.
move='M'
scent=I.to_i
end
else
# Hill not in sight.
# Test if marked position in sight.
map2.each_with_index { |row,y|
row.each_with_index { |el,x|
if el.to_i==I.to_i
goaly,goalx=y,x
see_goal=true
end
}
}
if see_goal
# Marked position in sight.
# Move towards marked position.
move = move_towards(goaly,goalx)
else
# No marked position in sight.
# Choose a random move.
move = move_random()
end
end
if move == 'M'
print move, " ", scent
else
print move
end
print "\n"
$stdout.flush()
end